diff --git a/contracts/companion/README.md b/contracts/companion/README.md index d88bc48..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. @@ -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..105f5d6 --- /dev/null +++ b/contracts/companion/run-invocation-interop.md @@ -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": "", + "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. diff --git a/contracts/companion/schemas/run-invocation.schema.json b/contracts/companion/schemas/run-invocation.schema.json new file mode 100644 index 0000000..ff86f0e --- /dev/null +++ b/contracts/companion/schemas/run-invocation.schema.json @@ -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 +} 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..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", @@ -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}")