From fc8f856b40119a5137e84aff92c24d6d84b2209e Mon Sep 17 00:00:00 2001 From: brettin Date: Fri, 5 Jun 2026 16:32:47 -0400 Subject: [PATCH 1/2] Implement RFC 001: RunInvocation companion schema and MAG tool_calls Add the normative contract changes proposed by RFC 001 (run invocation I/O and MAG tool_calls). Introduces an optional companion RunInvocation schema capturing literal inputs/parameters and outputs.tool_calls ([{ id, name, input }]) behind a spine ExecutionContext, without widening the mandatory spine. - Add companion/schemas/run-invocation.schema.json + example fixture - Register schema in core-v3-companion profile and validator mappings - Document OpenAI/Anthropic interop and MAG normalization Gap IDs: xrun-execution-context-io, xrun-outputs-tool-calls Co-authored-by: Cursor --- contracts/companion/README.md | 1 + contracts/companion/run-invocation-interop.md | 62 ++++++++ .../schemas/run-invocation.schema.json | 146 ++++++++++++++++++ .../companion/run-invocation.example.json | 53 +++++++ contracts/profiles/core-v3-companion.json | 3 +- scripts/validate_v3_contracts.py | 1 + 6 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 contracts/companion/run-invocation-interop.md create mode 100644 contracts/companion/schemas/run-invocation.schema.json create mode 100644 contracts/fixtures/companion/run-invocation.example.json diff --git a/contracts/companion/README.md b/contracts/companion/README.md index d88bc48..e3e3e89 100644 --- a/contracts/companion/README.md +++ b/contracts/companion/README.md @@ -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/`. diff --git a/contracts/companion/run-invocation-interop.md b/contracts/companion/run-invocation-interop.md new file mode 100644 index 0000000..4a8af56 --- /dev/null +++ b/contracts/companion/run-invocation-interop.md @@ -0,0 +1,62 @@ +# 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`. + +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": "", + "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_calls` batches 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 equals `ExecutionContext.configHash`; otherwise it is an opaque 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). diff --git a/contracts/companion/schemas/run-invocation.schema.json b/contracts/companion/schemas/run-invocation.schema.json new file mode 100644 index 0000000..1b1ad34 --- /dev/null +++ b/contracts/companion/schemas/run-invocation.schema.json @@ -0,0 +1,146 @@ +{ + "$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"], + "properties": { + "contextHash": { + "type": "string", + "minLength": 1, + "description": "Reproducibility hash that ties this invocation record to a spine ExecutionContext. Equal to execution-context.schema.json#configHash when a single canonical hash is used; otherwise an opaque digest agreed by orchestrator and platform." + }, + "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." + }, + "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." + }, + "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": ["name"], + "properties": { + "id": { + "type": "string", + "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": { + "description": "Decoded tool arguments object (already parsed from any provider-specific JSON string encoding)." + } + }, + "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 +} diff --git a/contracts/fixtures/companion/run-invocation.example.json b/contracts/fixtures/companion/run-invocation.example.json new file mode 100644 index 0000000..1683770 --- /dev/null +++ b/contracts/fixtures/companion/run-invocation.example.json @@ -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" + } +} diff --git a/contracts/profiles/core-v3-companion.json b/contracts/profiles/core-v3-companion.json index e6059ea..7de8d33 100644 --- a/contracts/profiles/core-v3-companion.json +++ b/contracts/profiles/core-v3-companion.json @@ -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": { diff --git a/scripts/validate_v3_contracts.py b/scripts/validate_v3_contracts.py index b647c0d..77fb759 100644 --- a/scripts/validate_v3_contracts.py +++ b/scripts/validate_v3_contracts.py @@ -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}") From 17bb7850ce0ed184531a32e72d69b753ded9676a Mon Sep 17 00:00:00 2001 From: Peng Ding Date: Sun, 7 Jun 2026 11:05:13 -0500 Subject: [PATCH 2/2] Tighten RunInvocation companion contract --- contracts/companion/README.md | 2 +- contracts/companion/run-invocation-interop.md | 10 +++++++--- .../schemas/run-invocation.schema.json | 19 ++++++++++++------- contracts/profiles/core-v3-companion.json | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/contracts/companion/README.md b/contracts/companion/README.md index e3e3e89..f4c5665 100644 --- a/contracts/companion/README.md +++ b/contracts/companion/README.md @@ -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. diff --git a/contracts/companion/run-invocation-interop.md b/contracts/companion/run-invocation-interop.md index 4a8af56..105f5d6 100644 --- a/contracts/companion/run-invocation-interop.md +++ b/contracts/companion/run-invocation-interop.md @@ -2,7 +2,7 @@ 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`. +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. @@ -55,8 +55,12 @@ Normalization rules: ## `maxToolCalls` semantics -Budget enforcement counts **model-returned `tool_calls` batches 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`. +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 equals `ExecutionContext.configHash`; otherwise it is an opaque 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). +`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. diff --git a/contracts/companion/schemas/run-invocation.schema.json b/contracts/companion/schemas/run-invocation.schema.json index 1b1ad34..ff86f0e 100644 --- a/contracts/companion/schemas/run-invocation.schema.json +++ b/contracts/companion/schemas/run-invocation.schema.json @@ -4,12 +4,12 @@ "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"], + "required": ["contextHash", "inputs"], "properties": { "contextHash": { "type": "string", - "minLength": 1, - "description": "Reproducibility hash that ties this invocation record to a spine ExecutionContext. Equal to execution-context.schema.json#configHash when a single canonical hash is used; otherwise an opaque digest agreed by orchestrator and platform." + "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", @@ -35,7 +35,8 @@ "enum": ["system", "user", "assistant", "tool"] }, "content": { - "description": "String content, or a list of typed content blocks (text/tool_use/tool_result) after MAG normalization." + "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": { @@ -96,17 +97,19 @@ "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." + "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": ["name"], + "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": { @@ -115,7 +118,9 @@ "description": "Tool name; must match a tools[].name advertised in inputs." }, "input": { - "description": "Decoded tool arguments object (already parsed from any provider-specific JSON string encoding)." + "type": "object", + "description": "Decoded tool arguments object (already parsed from any provider-specific JSON string encoding).", + "additionalProperties": true } }, "additionalProperties": false diff --git a/contracts/profiles/core-v3-companion.json b/contracts/profiles/core-v3-companion.json index 7de8d33..f9bfff0 100644 --- a/contracts/profiles/core-v3-companion.json +++ b/contracts/profiles/core-v3-companion.json @@ -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",