Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion contracts/companion/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GMP Companion Schemas

Optional JSON Schemas for **enterprise interoperability** that sit **outside** the mandatory `core-v3` profile defined in [profiles/core-v3.json](../profiles/core-v3.json). Use them when exchanging portable **campaign graphs**, **data-movement intents**, or **evaluation publication metadata** across sites—without widening the spine OpenAPI into a universal platform specification.
Optional JSON Schemas for **enterprise interoperability** that sit **outside** the mandatory `core-v3` profile defined in [profiles/core-v3.json](../profiles/core-v3.json). Use them when exchanging portable **campaign graphs**, **data-movement intents**, **evaluation publication metadata**, or **literal run invocation records** across sites—without widening the spine OpenAPI into a universal platform specification.

Deployments that commit to validating these artefacts may adopt **[profiles/core-v3-companion.json](../profiles/core-v3-companion.json)** in addition to the spine profile.

Expand All @@ -9,6 +9,7 @@ Deployments that commit to validating these artefacts may adopt **[profiles/core
| CampaignPlan | [schemas/campaign-plan.schema.json](schemas/campaign-plan.schema.json) | Typed DAG (nodes + edges + optional gate references); link from Run via `planRef` / `planDigest`. |
| DataMovementIntent | [schemas/data-movement-intent.schema.json](schemas/data-movement-intent.schema.json) | Declarative move/stage request; actual Globus/ObjectStore execution lives in mover **capabilities**. |
| EvalPublication | [schemas/eval-publication.schema.json](schemas/eval-publication.schema.json) | Publication / leaderboard metadata keyed to spine `suiteId` / `evalId` results. |
| RunInvocation | [schemas/run-invocation.schema.json](schemas/run-invocation.schema.json) | Literal invocation `inputs` / `parameters` and `outputs` (including MAG `tool_calls`) behind a spine `ExecutionContext`; link from Run via `contextHash`. See [run-invocation-interop.md](run-invocation-interop.md). |

**Versioning:** additive optional fields compatible with repo-wide JSON Schema conventions; companion `$id` URIs live under `https://gmp.dev/contracts/companion/schemas/`.

Expand Down
66 changes: 66 additions & 0 deletions contracts/companion/run-invocation-interop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# RunInvocation interop & MAG normalization

Implements [RFC 001 — Run invocation I/O and MAG `tool_calls`](../rfcs/001-run-invocation-and-tool-use.md).

The spine [`execution-context.schema.json`](../schemas/common/execution-context.schema.json) records reproducibility hashes only. Platform v0 additionally sends literal `inputs` (messages, tools, prompt) and `parameters` (model, system) and returns `outputs.tool_calls`. The optional companion [`RunInvocation`](schemas/run-invocation.schema.json) artefact captures that literal request/response payload **without widening the mandatory spine**. Orchestrators that only emit hashes can ignore it; deployments that need to persist or exchange the verbatim invocation reference it by `contextHash`. A `RunInvocation` records MAG/model turns; executed-tool telemetry remains in the event stream, and tool results can appear as later `role:"tool"` messages linked by `tool_call_id`.

This document defines how the Model/Agent Gateway (MAG) **normalizes** provider-specific OpenAI and Anthropic shapes into the single `RunInvocation` representation.

## Canonical shape

```jsonc
{
"contextHash": "<ties to spine ExecutionContext>",
"inputs": { "model", "system", "messages": [{ "role", "content" }], "tools": [{ "name", "description", "input_schema" }] },
"parameters": { "model", "system", "temperature", "max_tokens", ... },
"outputs": { "content", "tool_calls": [{ "id", "name", "input" }], "usage", "finish_reason", "model", "provider" }
}
```

The normative element is `outputs.tool_calls` as an array of `{ id, name, input }` (MAG `MagToolCall`), where `input` is the **decoded** arguments object.

## Messages (`inputs.messages`)

| Concept | OpenAI | Anthropic | RunInvocation (normalized) |
| --- | --- | --- | --- |
| Roles | `system`, `user`, `assistant`, `tool` | `user`, `assistant` (+ top-level `system`) | `system`, `user`, `assistant`, `tool` |
| System prompt | `messages[0]` with `role:"system"` | top-level `system` string | `inputs.system` / `parameters.system`, and/or a `role:"system"` message |
| Content | `content` string, or content parts | `content` string, or content blocks | string **or** list of typed blocks |
| Tool result | `role:"tool"` + `tool_call_id` | `user` message w/ `tool_result` block (`tool_use_id`) | `role:"tool"` + `tool_call_id` |

## Tools (`inputs.tools`)

| Field | OpenAI | Anthropic | RunInvocation |
| --- | --- | --- | --- |
| Name | `function.name` | `name` | `name` |
| Description | `function.description` | `description` | `description` |
| Args schema | `function.parameters` | `input_schema` | `input_schema` |

OpenAI's `function.parameters` is renamed to `input_schema` on normalization.

## Tool calls (`outputs.tool_calls`)

| Field | OpenAI | Anthropic | RunInvocation |
| --- | --- | --- | --- |
| Id | `tool_calls[].id` | `tool_use` block `id` | `id` |
| Name | `tool_calls[].function.name` | `tool_use` block `name` | `name` |
| Arguments | `tool_calls[].function.arguments` (**JSON string**) | `tool_use` block `input` (**object**) | `input` (**object**) |

Normalization rules:

1. **Parse arguments.** OpenAI `function.arguments` is a JSON-encoded string; MAG parses it into the `input` object. Anthropic `input` is already an object and is passed through.
2. **Preserve ids.** `id` is retained so a later `role:"tool"` result can reference it via `tool_call_id`.
3. **Validate names.** Each `tool_calls[].name` should match a `inputs.tools[].name`.
4. **`finish_reason`.** OpenAI `finish_reason:"tool_calls"` and Anthropic `stop_reason:"tool_use"` both normalize to a stop indicating tool use.

## `maxToolCalls` semantics

Budget enforcement counts **model-returned tool call items per MAG invoke** — i.e. `len(outputs.tool_calls)` — not local tool-agent executions. See the platform [`platform-v0-implementation-profile.json`](https://github.com/brettin/ARIAPlatform_v0/blob/main/reference/platform-v0-implementation-profile.json) `semantics.maxToolCalls`.

## Spine linkage

`RunInvocation.contextHash` ties back to a spine [`Run.executionContext`](../schemas/common/run.schema.json). When a single canonical hash is used it may equal the 64-hex `ExecutionContext.configHash`; otherwise it is a documented 32-hex platform digest agreed by orchestrator and platform. The spine `Run` is intentionally **not** widened — `RunInvocation` stays companion/optional, consistent with [`profiles/core-v3-companion.json`](../profiles/core-v3-companion.json).

## Sensitivity and retention

`RunInvocation` can contain literal prompts, messages, parameters, and decoded tool inputs. Deployments should treat these records as sensitive audit artefacts, apply access controls and retention policy, and redact or replace raw values with deployment-owned references or digests when raw invocation payloads cannot be stored.
151 changes: 151 additions & 0 deletions contracts/companion/schemas/run-invocation.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://gmp.dev/contracts/companion/schemas/run-invocation.schema.json",
"title": "RunInvocation",
"description": "Optional companion artefact that records the model-agnostic invocation inputs/parameters and outputs (including MAG tool_calls) behind a spine ExecutionContext. The spine Run carries reproducibility hashes only; deployments that need to persist or exchange the literal request/response payload reference this artefact by its contextHash. See run-invocation-interop.md for OpenAI/Anthropic mapping and MAG normalization rules.",
"type": "object",
"required": ["contextHash", "inputs"],
"properties": {
"contextHash": {
"type": "string",
"pattern": "^[a-fA-F0-9]{32}([a-fA-F0-9]{32})?$",
"description": "Opaque reproducibility digest that ties this invocation record to a spine ExecutionContext. It may equal execution-context.schema.json#configHash when a canonical 64-hex hash is used, or a platform-defined 32-hex digest when documented by the implementation profile."
},
"inputs": {
"type": "object",
"description": "Normalized request payload sent to the model/agent gateway (MAG). Carries any subset of prompt/message/messages plus declared tools and the selected model.",
"properties": {
"prompt": {
"type": "string",
"description": "Single-string prompt (legacy/completions style)."
},
"message": {
"type": "string",
"description": "Single user message convenience form when no full message array is supplied."
},
"messages": {
"type": "array",
"description": "Ordered chat transcript in MAG-normalized form. Provider-specific shapes (OpenAI roles, Anthropic content blocks) are normalized to {role, content}.",
"items": {
"type": "object",
"required": ["role"],
"properties": {
"role": {
"type": "string",
"enum": ["system", "user", "assistant", "tool"]
},
"content": {
"description": "String content, or a list of typed content blocks (text/tool_use/tool_result) after MAG normalization.",
"oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "object", "additionalProperties": true } }]
},
"name": { "type": "string" },
"tool_call_id": {
"type": "string",
"description": "Set on role=tool messages to correlate a tool result with the originating tool call id."
}
},
"additionalProperties": true
}
},
"tools": {
"type": "array",
"description": "Tool definitions advertised to the model. MAG normalizes OpenAI function tools and Anthropic tools to {name, description, input_schema}.",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string", "minLength": 1 },
"description": { "type": "string" },
"input_schema": {
"type": "object",
"description": "JSON Schema for the tool arguments (Anthropic naming; OpenAI 'parameters' is normalized to this key).",
"additionalProperties": true
}
},
"additionalProperties": true
}
},
"model": {
"type": "string",
"description": "Model identifier when supplied as part of inputs. Mirrors parameters.model; parameters.model is authoritative if both are present."
},
"system": {
"type": "string",
"description": "System prompt when carried inline with inputs (Anthropic top-level system). Mirrors parameters.system."
}
},
"additionalProperties": true
},
"parameters": {
"type": "object",
"description": "Decoding/control parameters for the invocation (model selection, system prompt, sampling). Open map; well-known keys are listed for interoperability.",
"properties": {
"model": { "type": "string" },
"system": { "type": "string" },
"temperature": { "type": "number" },
"top_p": { "type": "number" },
"max_tokens": { "type": "integer", "minimum": 1 },
"stop": {
"type": "array",
"items": { "type": "string" }
}
},
"additionalProperties": true
},
"outputs": {
"type": "object",
"description": "Normalized response payload returned by the MAG. tool_calls is the canonical, provider-agnostic representation of model-requested tool invocations.",
"properties": {
"content": {
"description": "Assistant content: a string, or a list of typed content blocks after MAG normalization.",
"oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "object", "additionalProperties": true } }]
},
"tool_calls": {
"type": "array",
"description": "Model-requested tool invocations as [{ id, name, input }]. OpenAI tool_calls[].function.{name,arguments} and Anthropic tool_use blocks {id,name,input} are normalized to this shape; arguments JSON strings are parsed into input objects.",
"items": {
"type": "object",
"required": ["id", "name", "input"],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "Provider-assigned identifier for this tool call, echoed back on the corresponding role=tool result via tool_call_id."
},
"name": {
"type": "string",
"minLength": 1,
"description": "Tool name; must match a tools[].name advertised in inputs."
},
"input": {
"type": "object",
"description": "Decoded tool arguments object (already parsed from any provider-specific JSON string encoding).",
"additionalProperties": true
}
},
"additionalProperties": false
}
},
"usage": {
"type": "object",
"description": "Token/cost usage as reported by the provider (e.g. input_tokens, output_tokens). Open map.",
"additionalProperties": true
},
"finish_reason": {
"type": "string",
"description": "Normalized stop reason (e.g. stop, length, tool_calls/tool_use)."
},
"model": {
"type": "string",
"description": "Resolved model that produced the response."
},
"provider": {
"type": "string",
"description": "Upstream provider that served the invocation (e.g. openai, anthropic)."
}
},
"additionalProperties": true
}
},
"additionalProperties": false
}
53 changes: 53 additions & 0 deletions contracts/fixtures/companion/run-invocation.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"contextHash": "a3e5d2571606f4f8a13f0f248f54f4e35f3e7f56d76d5f7c7a82f5f6567a0f84",
"inputs": {
"model": "claude-sonnet-4",
"system": "You are a fluid-dynamics simulation assistant.",
"messages": [
{
"role": "user",
"content": "Run a Kolmogorov flow simulation at Reynolds number 1000 and report the energy spectrum."
}
],
"tools": [
{
"name": "run_simulation",
"description": "Execute a registered HPC simulation capability.",
"input_schema": {
"type": "object",
"required": ["capabilityId", "reynolds"],
"properties": {
"capabilityId": { "type": "string" },
"reynolds": { "type": "number" }
}
}
}
]
},
"parameters": {
"model": "claude-sonnet-4",
"system": "You are a fluid-dynamics simulation assistant.",
"temperature": 0,
"max_tokens": 1024
},
"outputs": {
"content": "I'll run the Kolmogorov flow simulation now.",
"tool_calls": [
{
"id": "toolu_01A9F2C3D4E5",
"name": "run_simulation",
"input": {
"capabilityId": "cap.simulation.kolmogorov.v1",
"reynolds": 1000
}
}
],
"usage": {
"input_tokens": 312,
"output_tokens": 47
},
"finish_reason": "tool_calls",
"model": "claude-sonnet-4",
"provider": "anthropic"
}
}
5 changes: 3 additions & 2 deletions contracts/profiles/core-v3-companion.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"profileId": "core-v3-companion",
"version": "3.0.1",
"description": "Optional bundle: mandates spine core-v3 artefacts plus CampaignPlan / DataMovementIntent / EvalPublication companion JSON Schemas for cross-enterprise interoperability.",
"description": "Optional bundle: mandates spine core-v3 artefacts plus CampaignPlan / DataMovementIntent / EvalPublication / RunInvocation companion JSON Schemas for cross-enterprise interoperability.",
"requiredApiSpec": "../openapi/gmp-core-v3.yaml",
"requiredOperations": [
"establishSession",
Expand Down Expand Up @@ -91,7 +91,8 @@
"../schemas/events/classification-shift-event.schema.json",
"../companion/schemas/campaign-plan.schema.json",
"../companion/schemas/data-movement-intent.schema.json",
"../companion/schemas/eval-publication.schema.json"
"../companion/schemas/eval-publication.schema.json",
"../companion/schemas/run-invocation.schema.json"
],
"reproducibilityTiers": {
"tier1_declared": {
Expand Down
1 change: 1 addition & 0 deletions scripts/validate_v3_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ def main() -> int:
("campaign-plan.example.json", companion_schema_dir / "campaign-plan.schema.json"),
("data-movement-intent.example.json", companion_schema_dir / "data-movement-intent.schema.json"),
("eval-publication.example.json", companion_schema_dir / "eval-publication.schema.json"),
("run-invocation.example.json", companion_schema_dir / "run-invocation.schema.json"),
]
if not FIXTURES_COMPANION.exists():
errors.append(f"Missing companion fixtures directory: {FIXTURES_COMPANION}")
Expand Down