diff --git a/.gitignore b/.gitignore index ef27f8b..624a9e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .idea/ .git/ book/ +site/ +references/ .DS_Store \ No newline at end of file diff --git a/docs/acs.md b/docs/acs.md index 9c6d1cc..4119d4f 100644 --- a/docs/acs.md +++ b/docs/acs.md @@ -1,23 +1,21 @@ # Agent Control Standard -The Agent Control Standard (ACS) provides specification for building [trustworthy agents](./README.md). -Agents that implement ACS can be deployed with higher trust. -They are instrumentable, traceable and inspectable. -They are an open book +The Agent Control Standard (ACS) is a wire-format specification that lets a separate **Guardian Agent** permit, deny, or modify what an AI agent does — in real time, with a verifiable audit trail. -They have a dynamic bill-of-material, a clear audit trail and hard inline-controls. +Agents that implement ACS are instrumentable (Guardians can intervene at runtime), traceable (every step emits an OpenTelemetry span and an OCSF event), and inspectable (a dynamic Agent Bill of Materials enumerates models, MCP servers, A2A peers, tools, knowledge sources, and memory stores). - +ACS extends existing standards rather than reinventing them: JSON-RPC 2.0 for the wire format, OpenTelemetry and OCSF for observability, CycloneDX / SPDX / SWID for the AgBOM, MCP and A2A intact for tool and peer communication. -Trustworthiness of agents builds upon the foundation of existing standards (MCP and A2A), but provides value regardless. -It build upon cybersecurity and observability standards including OpenTelemetry, OCSF, CycloneDX, SPDX and SWID. +## What v0.1.0 ships -ACS makes agents trustworthy. +- **ACS-Core** (mandatory baseline) — capability-negotiation handshake, JSON-RPC envelope, 16 native lifecycle hooks (`sessionStart`/`End`, `agentTrigger`, `userMessage`, `agentResponse`, `turnStart`/`End`, `toolCallRequest`/`Result`, `knowledgeRetrieval`, `memoryContextRetrieval`, `memoryStore`, `preCompact`/`postCompact`, `subagentStart`/`Stop`), wrapped MCP, five dispositions (`allow`, `deny`, `modify`, `ask`, `defer`), SessionContext with rolling SHA-256 chain hash, optional Intent with immutability rule, replay protection, and `system/ping` liveness. +- **ACS-Trace** profile — OpenTelemetry semconv mapping + OCSF event-class mapping, with decisions emitted as span events on the parent step span. +- **ACS-Inspect** / **ACS-Inspect-Dynamic** profiles — canonical AgBOM with `agbom/snapshot` and `agbom/changed`, deterministic CycloneDX / SPDX / SWID derivations. +- **ACS-Provenance** profile — field-level `Provenance` objects with `origin`, `source_id`, `derived_from`, and an OPTIONAL wire-format `trust` enum that obeys the monotonicity rule. +- **ACS-Crypto** profile — crypto-agile signature registry: HMAC-SHA256 baseline, ML-DSA-65 / SLH-DSA-128s for PQC, hybrid composites for transitional deployments. +- **ACS-Audit** profile — `request_hash` on every ContextEntry so the chain commits to request content, not just step metadata. -!!! info "Work in progress" - This page is currently under development. - - **Want to contribute?** Check out the [GitHub issue](https://github.com/Agent-Control-Standard/ACS/issues/53) and join the discussion! +See [Conformance Profiles](./spec/conformance.md) for what each profile requires. ## Trustworthy agents are diff --git a/docs/spec/conformance.md b/docs/spec/conformance.md new file mode 100644 index 0000000..eb4ba9e --- /dev/null +++ b/docs/spec/conformance.md @@ -0,0 +1,83 @@ +# Conformance Profiles + +ACS v0.1.0 conformance is tiered. Every conformant deployment implements **ACS-Core** — the mandatory baseline below. Trace event emission, AgBOM serialization, field-level provenance, cryptographic signatures, and strengthened audit chains are organized as **profiles** that deployments declare independently in the [handshake](./instrument/specification.md#4-capability-negotiation-handshake). + +The profile system lets a small harness ship a useful subset of ACS quickly, while a more capable deployment can layer on observability, supply-chain inventory, or cryptographic integrity without changing the wire format. + +## Profile declaration + +Profiles are declared in the handshake. ClientHello includes `profiles_supported: string[]`; ServerHello includes `profiles_accepted: string[]`. Profile names are the lowercase hyphenated forms below: `acs-core`, `acs-trace`, `acs-inspect`, `acs-inspect-dynamic`, `acs-provenance`, `acs-crypto`, `acs-audit`. + +A Guardian MAY refuse a session if the client does not declare a profile the Guardian's policy requires (e.g. a Guardian whose policy needs provenance MAY refuse a client that does not declare `acs-provenance`). + +## ACS-Core (mandatory baseline) + +A v0.1.0-conformant deployment MUST implement ACS-Core. ACS-Core comprises: + +- **Handshake** — `handshake/hello` with ClientHello/ServerHello ([Specification §4](./instrument/specification.md#4-capability-negotiation-handshake)). +- **Request/response envelope** — JSON-RPC 2.0 with ACS extensions ([§3](./instrument/specification.md#3-wire-format)). `request_id`, `timestamp`, `acs_version`, `metadata` required on every request. +- **Hook taxonomy** — At minimum: `sessionStart`, `userMessage` or `agentTrigger`, `toolCallRequest`, `toolCallResult`, `agentResponse`, `sessionEnd`. Additional hooks (`turnStart`/`turnEnd`, `preCompact`/`postCompact`, `subagentStart`/`subagentStop`, `knowledgeRetrieval`, `memoryContextRetrieval`, `memoryStore`) are normatively defined and SHOULD be implemented when the harness can observe the corresponding event; they are capability-negotiated via the handshake. +- **Dispositions** — All five (ALLOW, DENY, MODIFY, ASK, DEFER) with required fields per [§6](./instrument/specification.md#6-disposition-vocabulary). +- **SessionContext and Intent** — `session_id`, `chain_hash` (rolling SHA-256), append-only ContextEntry chain ([§8](./instrument/specification.md#8-sessioncontext-and-intent)). Intent is optional but normative when IBAC is the enforcement paradigm. +- **Replay protection** — `request_id` (UUID) and `timestamp` on every request; Guardians MUST reject replays per [§10.3](./instrument/specification.md#103-replay-protection). +- **Liveness** — `system/ping` ([§13](./instrument/specification.md#13-liveness-system-methods)). +- **Wrapped MCP** — `protocols/MCP/*` ([Hooks](./instrument/hooks.md#protocolsmcp)). + +ACS-Core does NOT require: field-level Provenance objects, Trace event emission, AgBOM, cryptographic signatures, or `request_hash` on ContextEntry (`request_hash` remains SHOULD). + +## ACS-Trace + +Adds deterministic Trace event emission per [Trace Events](./trace/events.md). A deployment claiming ACS-Trace MUST: + +1. Emit at least one of {OTel, OCSF} for every supported ACS step, with the required attributes populated. +2. Record decisions as Trace events. +3. Carry provenance facts forward onto Trace events. + +Required for deployments that need cross-vendor observability or SIEM integration. Trace events MUST NOT block enforcement. + +## ACS-Inspect + +Adds AgBOM snapshot emission per [Inspect](./inspect/README.md). A deployment claiming ACS-Inspect MUST: + +1. Emit `agbom/snapshot` once per session before content-bearing hooks fire. +2. Have the Guardian serialize the canonical AgBOM into at least one of {CycloneDX 1.6, SPDX 3.0, SWID} on request. + +Required when Guardian policy depends on component inventory. + +### ACS-Inspect-Dynamic (extends ACS-Inspect) + +Adds `agbom/changed` emission on every mid-session component mutation, with audit-chain integration. Required for deployments where agents hot-swap models, tools, or MCP servers. + +## ACS-Provenance + +Adds field-level Provenance objects ([§7](./instrument/specification.md#7-provenance)) to data-bearing fields. A deployment claiming ACS-Provenance MUST populate `provenance_id`, `origin`, and (when applicable) `derived_from` on Provenance-bearing fields. + +The wire-format `trust` enum is OPTIONAL in v0.1; the v0.1 expected practice is for Guardians to derive trust from `origin` + `source_id` against local policy without populating the field. Vendor Guardian implementations that elect to populate `trust` on the wire MUST enforce the monotonicity rule on `agent_generated` trust and SHOULD use the default channel-to-trust mapping ([§7.2](./instrument/specification.md#72-default-channel-to-trust-mapping)) so cross-deployment audits remain portable. + +Required for FIDES, CaMeL, and AARM-style enforcement paradigms (which depend on the trust *concept* — whether materialized on the wire or derived in policy). Not required for pure IBAC. + +## ACS-Crypto + +Adds cryptographic signature support beyond the baseline's replay-protection fields. A deployment claiming ACS-Crypto MUST support at least `ML-DSA-65` (RECOMMENDED primary) and SHOULD support `SLH-DSA-128s` as an algorithmic-diversity backup. Hybrid composites (`ML-DSA-65+ECDSA-P256`, `ML-DSA-65+RSA-PSS-SHA256`) are OPTIONAL for transitional deployments. + +Baseline deployments without ACS-Crypto MAY use `HMAC-SHA256` for integrity or rely on transport-level security; neither is required by ACS-Core. + +## ACS-Audit + +Strengthens the audit chain beyond ACS-Core's baseline. A deployment claiming ACS-Audit MUST populate `request_hash` (lowercase-hex SHA-256 of JCS-canonicalized request params) on every ContextEntry, ensuring the chain commits to request content, not just step metadata. ACS-Audit deployments SHOULD also populate `timestamp` and `provenance_summary` on every ContextEntry. + +## Profile combinations + +Profiles compose. A deployment that wants full observability and supply-chain inventory but not cryptographic signatures declares `["acs-core", "acs-trace", "acs-inspect", "acs-provenance"]`. A high-assurance deployment declares all of `["acs-core", "acs-trace", "acs-inspect", "acs-inspect-dynamic", "acs-provenance", "acs-crypto", "acs-audit"]`. A minimal IDE harness declares `["acs-core"]` only. + +## Quick reference + +| Profile | What it adds | When to claim | +|---|---|---| +| `acs-core` | Handshake, envelope, hook taxonomy, dispositions, SessionContext, Intent, replay protection, ping | Always (mandatory) | +| `acs-trace` | OTel + OCSF event emission per step | Cross-vendor observability or SIEM integration | +| `acs-inspect` | `agbom/snapshot` + canonical AgBOM serialization | Policy depends on component inventory | +| `acs-inspect-dynamic` | `agbom/changed` on mutation | Agent hot-swaps components mid-session | +| `acs-provenance` | Field-level Provenance objects | Enforcing FIDES, CaMeL, or AARM-style information-flow paradigms | +| `acs-crypto` | ML-DSA-65 / SLH-DSA-128s signatures | Cryptographic integrity beyond shared-secret HMAC | +| `acs-audit` | `request_hash` on every ContextEntry | Chain must commit to request content | diff --git a/docs/spec/inspect/README.md b/docs/spec/inspect/README.md index aadc01c..e6302b8 100644 --- a/docs/spec/inspect/README.md +++ b/docs/spec/inspect/README.md @@ -1,45 +1,77 @@ -# Agent Control Standard - Inspect with AgBOM - -As AI agents become more sophisticated, transparent insight into their architecture, behavior, and security posture becomes critical. The Agent Bill of Materials (AgBOM) addresses this need by providing a structured, dynamic inventory of all components comprising an agent system including tools, models, capabilities, and dependencies. This concept aligns with growing calls for AI system transparency and supply chain integrity, particularly within regulated or enterprise environments. - -!!! info "AgBOM Extends Industry Standards" - We already have great Bill-of-Material standards, so ACS doesn't introduce a new one. Instead, it extends existing industry-proven standards: CycloneDX, SPDX, and SWID to support AI agent-specific components. - -## What Is AgBOM? -AgBOM, short for Agent Bill-of-Materials, is a comprehensive inventory that captures metadata about every component in an AI agent system. Its core purpose is to enable inspectability, allowing developers, auditors, and stakeholders to determine: -- What tools, models, and capabilities are embedded within an agent -- Who authored each component -- What version and configuration is currently deployed -- What external services and data sources are accessed - -This visibility supports better security tracing, version tracking, and regulatory compliance. AgBOM must dynamically adapt to reflect the rapid iteration and evolution of agent architectures, especially in real-time or distributed environments. - -## Desired Outcome -The end result of generating an AgBOM is a standardized, machine-readable artifact that outlines the full software composition of the agent. -To support industry-wide adoption and interoperability, AgBOM supports output in the following standard formats: - -| BOM standard | AgBOM Spec | Status | -|--|--|--| -| [CycloneDX](https://cyclonedx.org/) | [AgBOM with CycloneDX](./extend_cyclonedx.md) | Working draft | -| [SPDX](https://spdx.dev/) | [AgBOM with SPDX](./extend_spdx.md) | [Help wanted](https://github.com/Agent-Control-Standard/ACS/issues/20) | -| [SWID](https://csrc.nist.gov/Projects/Software-Identification-SWID) | [AgBOM with SWID](./extend_swid.md) | [Help wanted](https://github.com/Agent-Control-Standard/ACS/issues/21) | - -### AgBOM entities and parameters: - -| Entity | Parameters | -|--|--| -| Standard Packages | Name, Description, Version | -| Models | Name, Version, Description, Endpoint, Context Window, Args | -| Capabilities | Agent Card Definitions (per A2A), list of discovered Agents, list of MCP servers and parameters (protocolVersion, capabilities, serverInfo) | -| Knowledge | Name, Description, Schema, Search type, Search args | -| Memory | Name, Description, Type, Size, Search args, Window size, Path | -| Tools | Name, Description, Scheme, Endpoint (local/directly-attached and MCP) | - -### Triggers for AgBOM Update - -- Agent discovered, removed or changed capabilities -- MCP server discovered, removed or changed capabilities -- Knowledge discovered, removed or changed capabilities -- Tool discovered, removed or changed capabilities -- Memory discovered, removed or changed capabilities -- Model discovered, removed or changed capabilities +# Inspect — AgBOM + +AI agents add and remove capabilities at runtime: a model swap mid-session, a hot-loaded MCP server, a new A2A peer, a knowledge source registered after `sessionStart`. Without inspectability, every Guardian policy that depends on what the agent *is* (rather than only what it *does*) becomes unverifiable. + +The **Agent Bill of Materials (AgBOM)** is a queryable, dynamic inventory of the components an Observed Agent uses: Models, MCP servers, A2A peers, Tools, Knowledge sources, Memory stores, and Agent capabilities. + +!!! info "AgBOM extends industry standards" + ACS doesn't introduce a new BOM format. The canonical AgBOM is a structured component graph; CycloneDX, SPDX, and SWID outputs are deterministic derivations of the same graph. + +AgBOM emission is the subject of the **ACS-Inspect** [conformance profile](../conformance.md#acs-inspect). When Guardian policy depends on component inventory (e.g. banning a model or tool at the boundary), the deployment MUST implement ACS-Inspect. + +## Wire methods + +The `agbom/*` namespace is reserved by [Specification §3](../instrument/specification.md#3-wire-format). Two methods are defined in v0.1.0: + +| Method | Direction | Trigger | +|---|---|---| +| `agbom/snapshot` | Observed → Guardian | Once after `sessionStart`, before any content-bearing hook; and after any handshake renegotiation. Carries the full AgBOM. | +| `agbom/changed` | Observed → Guardian | Whenever a component is added, removed, or version-changed mid-session. Carries either a full snapshot or a diff (`added[]`, `removed[]`, `changed[]`). | + +Both methods follow the standard ACS request envelope and are written into the SessionContext audit chain — AgBOM mutation is part of the security-relevant history of the session. Decisions are normally `allow`; Guardians MAY return `deny` to refuse a session whose component graph contains a banned component, or to block a hot-swap. + +`agbom/changed` is part of the **ACS-Inspect-Dynamic** profile extension. Deployments that claim only **ACS-Inspect** emit a single snapshot and do not track mid-session mutations. + +## Canonical schema + +Every component graph is expressed in the canonical AgBOM document ([`agbom/document.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/agbom/document.json)). Components are typed; the type set in v0.1.0 is: + +| Type | Required fields | Optional fields | +|---|---|---| +| `model` | `id`, `name`, `version`, `provider`, `endpoint` (URI), `context_window` | `args` (model-config snapshot) | +| `mcp_server` | `id`, `name`, `version`, `endpoint` (URI), `tools[]` | — | +| `a2a_peer` | `id`, `endpoint` (URI), `protocol_version` | `agent_card_ref` (URI to peer's Agent Card if known) | +| `tool` | `id`, `name`, `version`, `provider`, `capability` (abstract: `filesystem.delete`, `network.egress`, `process.execute`, …) | — | +| `knowledge_source` | `id`, `name`, `source_type` (`vector_db`/`search_index`/`knowledge_base`/`web_search`/`other`) | `endpoint` (URI), `schema_ref` (URI) | +| `memory_store` | `id`, `name`, `scope` (`session`/`user`/`tenant`/`global`), `store_type` | `path` (URI), `window_size` | +| `agent_capability` | `id`, `name`, `description` | `tools[]`, `mcp_servers[]`, `a2a_peers[]` | + +The full per-component schema is [`agbom/component.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/agbom/component.json). + +Every component SHOULD carry `registration_provenance` (who declared it — framework / configuration / runtime discovery) so AgBOM mutations are traceable in the same lineage system as data flow. Deployments claiming **ACS-Provenance** MUST populate `registration_provenance` on every component. + +## Output format mappings + +The canonical document is the source of truth; serialized output is a deterministic derivation. The mapping rules live in [`inspect/format-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/inspect/format-mapping.json). + +| Standard | ACS extension | Status | +|---|---|---| +| [CycloneDX 1.6](https://cyclonedx.org/) | [Extending CycloneDX](./extend_cyclonedx.md) | Working draft | +| [SPDX 3.0](https://spdx.dev/) | [Extending SPDX](./extend_spdx.md) | Working draft | +| [SWID](https://csrc.nist.gov/Projects/Software-Identification-SWID) | [Extending SWID](./extend_swid.md) | Working draft | + +A Guardian MAY request a specific serialization in the handshake's AgBOM negotiation (`agbom_serializations_supported` in ServerHello); the canonical form is always the source of truth and the serializations are derivations. + +## ACS-Inspect conformance bar + +A deployment claiming **ACS-Inspect** MUST: + +1. Emit `agbom/snapshot` once per session, before content-bearing hooks fire. +2. Have the Guardian accept `agbom/snapshot`, write it into the SessionContext audit chain, and serialize the canonical AgBOM into at least one of {CycloneDX 1.6, SPDX 3.0, SWID} on request. + +A deployment claiming **ACS-Inspect-Dynamic** additionally MUST: + +3. Emit `agbom/changed` on every mutation to the component graph. +4. Write those mutations into the audit chain. + +Without -Dynamic, a deployment that hot-swaps components silently is not Inspect-conformant for that session — the Guardian's view of the agent diverges from runtime reality. + +## Triggers for AgBOM updates + +- Model added, removed, or version-changed. +- MCP server discovered, removed, or version-changed. +- A2A peer registered or deregistered. +- Tool registered, removed, or capability-changed. +- Knowledge source connected or disconnected. +- Memory store attached or detached. +- Agent capability declared or revoked. diff --git a/docs/spec/inspect/extend_cyclonedx.md b/docs/spec/inspect/extend_cyclonedx.md index 32a33e9..cea7207 100644 --- a/docs/spec/inspect/extend_cyclonedx.md +++ b/docs/spec/inspect/extend_cyclonedx.md @@ -1,11 +1,20 @@ -# AgBOM with CycloneDX +# Extending CycloneDX -!!! info "Work in progress" - This specification is currently under development. We're working on defining how AgBOM extends SPDX to support AI agent components. - - **Want to contribute?** Check out the [GitHub issue](https://github.com/Agent-Control-Standard/ACS/issues/22) and join the discussion! +CycloneDX 1.6 is one of the three normative AgBOM serializations in v0.1.0. The canonical AgBOM document is the source of truth; CycloneDX output is derived deterministically from it. The mapping rules live in [`inspect/format-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/inspect/format-mapping.json). -Agent Bill of Material example using CycloneDX +## Component-type mapping + +Each canonical component becomes one entry in the CycloneDX `components[]` array. CycloneDX `bom-ref` equals the canonical component `id`, which makes round-tripping straightforward. + +| Canonical type | CycloneDX `type` | Notes | +|---|---|---| +| `model` | `ai-model` | Uses CycloneDX's bom-types extension for AI models. | +| `mcp_server` | `service` | An endpoint reachable via MCP. | +| `a2a_peer` | `service` | Cross-agent endpoint; `agent_card_ref` flows into `externalReferences`. | +| `tool` | `application` | Agent-callable code unit. Capability flows into `properties`. | +| `knowledge_source` | `service` | A datastore or search endpoint. | +| `memory_store` | `service` | Long-lived state store. | +| `agent_capability` | `application` | A composed capability — its tool/MCP/A2A dependencies become CycloneDX `dependencies`. | ## Example @@ -15,71 +24,51 @@ Agent Bill of Material example using CycloneDX "specVersion": "1.6", "version": 1, "metadata": { - "timestamp": "2025-05-19T12:00:00Z", - "tools": [ - {"name": "cyclonedx-python-lib", "version": "6.2.1"} - ], - "authors": [ - {"name": "AgentOps Team", "email": "agentops@example.com"} - ] + "timestamp": "2026-04-30T10:30:00Z", + "tools": [{ "name": "acs-guardian", "version": "0.1.0" }], + "component": { "type": "application", "bom-ref": "urn:agent:finance-summary-agent" } }, "components": [ { - "type": "service", - "name": "finance-summary-agent", - "version": "1.2.3", - "bom-ref": "urn:agent:finance-summary-agent", + "type": "ai-model", + "bom-ref": "gpt-4o-2024-08-06", + "name": "GPT-4o", + "version": "2024-08-06", + "supplier": { "name": "OpenAI" }, "properties": [ - {"name": "a2aCardUrl", "value": "https://agent.example.com/.well-known/agent.json"}, - {"name": "languageRuntime", "value": "Python 3.10.9"}, - {"name": "environment.os", "value": "Ubuntu 22.04"}, - {"name": "environment.architecture", "value": "x86_64"}, - {"name": "model", "value": "gpt-4-32k"}, - {"name": "modelContextWindow", "value": "32768"}, - {"name": "memoryBackend", "value": "Pinecone"}, - {"name": "memoryLimitMB", "value": "2048"}, - {"name": "compliance", "value": "SOC2, GDPR"} + { "name": "acs:context_window", "value": "128000" }, + { "name": "acs:endpoint", "value": "https://api.openai.com/v1" } ] }, { - "type": "tool", - "name": "WebSearchAPI", - "version": "v1", - "bom-ref": "urn:tool:websearchapi", - "properties": [ - {"name": "description", "value": "External web search via Bing API"}, - {"name": "endpoint", "value": "https://api.bing.microsoft.com/v7.0/search"}, - {"name": "auth", "value": "API key"}, - {"name": "scope", "value": "read-only"}, - {"name": "timeoutMs", "value": "3000"} - ] + "type": "service", + "bom-ref": "urn:mcp:db-mcp", + "name": "db-mcp", + "version": "1.4.0", + "endpoints": ["https://mcp.internal/db"] }, { - "type": "tool", - "name": "PythonREPL", - "version": "1.2", - "bom-ref": "urn:tool:pythonrepl", + "type": "application", + "bom-ref": "urn:tool:database_query", + "name": "database_query", + "version": "1.4.0", "properties": [ - {"name": "description", "value": "Sandboxed Python evaluator"}, - {"name": "sandbox", "value": "true"}, - {"name": "memoryLimitMB", "value": "128"} + { "name": "acs:capability", "value": "datastore.read" }, + { "name": "acs:registration_provenance.origin", "value": "configuration" } ] } ], "dependencies": [ { "ref": "urn:agent:finance-summary-agent", - "dependsOn": [ - "urn:tool:websearchapi", - "urn:tool:pythonrepl" - ] - } - ], - "signatures": [ - { - "value": "", - "keyId": "agent-signing-key" + "dependsOn": ["gpt-4o-2024-08-06", "urn:mcp:db-mcp", "urn:tool:database_query"] } ] } ``` + +## Notes + +- Canonical fields without a direct CycloneDX home land in `properties` under the `acs:` prefix. This keeps the serialization round-trip-safe — anything a Guardian needs for policy is reachable from CycloneDX without consulting the canonical document separately. +- `registration_provenance` becomes a property pair (`acs:registration_provenance.origin`, `acs:registration_provenance.source_id`) so audits can detect components added by runtime discovery vs. configuration. +- For `agbom/changed`, deployments MAY emit a CycloneDX VEX-style diff or simply emit a fresh full serialization — the canonical wire form already carries the diff structure (`added[]`, `removed[]`, `changed[]`). diff --git a/docs/spec/inspect/extend_spdx.md b/docs/spec/inspect/extend_spdx.md index c37bd9d..7f3518c 100644 --- a/docs/spec/inspect/extend_spdx.md +++ b/docs/spec/inspect/extend_spdx.md @@ -1,6 +1,31 @@ -# AgBOM with SPDX +# Extending SPDX -!!! warning "Help wanted" - This specification is currently under development. We're working on defining how AgBOM extends SPDX to support AI agent components. - - **Want to contribute?** Check out the [GitHub issue](https://github.com/Agent-Control-Standard/ACS/issues/20) and join the discussion! +SPDX 3.0 is one of the three normative AgBOM serializations in v0.1.0. The canonical AgBOM document is the source of truth; SPDX output is derived deterministically from it. The mapping rules live in [`inspect/format-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/inspect/format-mapping.json). + +## Component-type mapping + +Each canonical component becomes one SPDX `software_Package` or `Service` node. Relationships (`DEPENDS_ON`, `DESCRIBES`, `USES`) reflect the agent_capability/tool/mcp_server graph. + +| Canonical type | SPDX class | Notes | +|---|---|---| +| `model` | `ai_AIPackage` | SPDX 3.0 AI profile node for model artifacts. | +| `mcp_server` | `Service` | Endpoint reachable via MCP. | +| `a2a_peer` | `Service` | Cross-agent endpoint; `agent_card_ref` flows into `externalRef`. | +| `tool` | `software_Package` | Agent-callable code unit. | +| `knowledge_source` | `Service` | Datastore or search endpoint. | +| `memory_store` | `Service` | Long-lived state store. | +| `agent_capability` | `software_Package` | Composed capability; its tool/MCP/A2A dependencies become `DEPENDS_ON` relationships. | + +## Relationships + +| ACS edge | SPDX relationship | +|---|---| +| `agent_capability.tools[]` | `agent_capability USES tool` | +| `agent_capability.mcp_servers[]` | `agent_capability USES mcp_server` | +| `agent_capability.a2a_peers[]` | `agent_capability USES a2a_peer` | +| `mcp_server.tools[]` | `mcp_server CONTAINS tool` | +| Root agent → all components | `agent DEPENDS_ON ` | + +## Status + +Working draft. The full SPDX 3.0 JSON-LD profile bindings are evolving alongside SPDX's AI profile work. diff --git a/docs/spec/inspect/extend_swid.md b/docs/spec/inspect/extend_swid.md index 8d42d51..8bae9ae 100644 --- a/docs/spec/inspect/extend_swid.md +++ b/docs/spec/inspect/extend_swid.md @@ -1,6 +1,21 @@ -# AgBOM with SWID +# Extending SWID -!!! warning "Help wanted" - This specification is currently under development. We're working on defining how AgBOM extends SWID to support AI agent components. - - **Want to contribute?** Check out the [GitHub issue](https://github.com/Agent-Control-Standard/ACS/issues/21) and join the discussion! +SWID (ISO/IEC 19770-2) is one of the three normative AgBOM serializations in v0.1.0. The canonical AgBOM document is the source of truth; SWID tags are derived deterministically from it. The mapping rules live in [`inspect/format-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/inspect/format-mapping.json). + +## Component-type mapping + +Each canonical component becomes one SoftwareIdentity tag with `tagId` = component `id` and `name` = component `name`, plus a `Link` element pointing back to the canonical AgBOM document so consumers can reach the full structured graph. + +| Canonical type | SWID role | Notes | +|---|---|---| +| `model` | `softwareCreator` | The model identifies itself as a software artifact whose creator is the provider. | +| `mcp_server` | `softwareCreator` | The MCP server's vendor. | +| `a2a_peer` | `softwareCreator` | When known, the peer's identity. | +| `tool` | `softwareCreator` | The tool's vendor. | +| `knowledge_source` | `softwareCreator` | The knowledge-source vendor. | +| `memory_store` | `softwareCreator` | The memory-store vendor. | +| `agent_capability` | `aggregator` | Composed capabilities aggregate their tool/MCP/A2A children. | + +## Status + +Working draft. SWID's strength here is its compactness for environments that already consume SWID via management tooling (NIST SCAP, asset inventories); CycloneDX and SPDX cover most agent-deployment use cases. diff --git a/docs/spec/instrument/a2a/hooks/cancel_task_request.md b/docs/spec/instrument/a2a/hooks/cancel_task_request.md index 7717d77..47bcf86 100644 --- a/docs/spec/instrument/a2a/hooks/cancel_task_request.md +++ b/docs/spec/instrument/a2a/hooks/cancel_task_request.md @@ -4,10 +4,10 @@ This hook is called when the observed client agent sends task cancellation messa This hook **must** be used before the observed agent sends the A2A-compliant message to server agent. #### 2. Method -[`tasks/cancel`](specification.md#48-a2a-protocol-methods) +`tasks/cancel` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | @@ -97,10 +97,10 @@ This hook is called when the observed server agent receives task cancellation me This hook **must** be used before the observed agent receives the A2A-compliant message. #### 2. Method -[`tasks/cancel`](specification.md#48-a2a-protocol-methods) +`tasks/cancel` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | diff --git a/docs/spec/instrument/a2a/hooks/get_task_push_notification_config_request.md b/docs/spec/instrument/a2a/hooks/get_task_push_notification_config_request.md index d6b5095..44ee148 100644 --- a/docs/spec/instrument/a2a/hooks/get_task_push_notification_config_request.md +++ b/docs/spec/instrument/a2a/hooks/get_task_push_notification_config_request.md @@ -3,10 +3,10 @@ This hook **must** be used before the observed agent sends the A2A-compliant message to server agent. #### 2. Method -[`tasks/pushNotificationConfig/get`](specification.md#48-a2a-protocol-methods) +`tasks/pushNotificationConfig/get` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | @@ -95,10 +95,10 @@ The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse This hook **must** be used before the observed server agent receives the A2A-compliant message. #### 2. Method -[`tasks/pushNotificationConfig/get`](specification.md#48-a2a-protocol-methods) +`tasks/pushNotificationConfig/get` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | diff --git a/docs/spec/instrument/a2a/hooks/get_task_request.md b/docs/spec/instrument/a2a/hooks/get_task_request.md index 43ef0ab..1963f5d 100644 --- a/docs/spec/instrument/a2a/hooks/get_task_request.md +++ b/docs/spec/instrument/a2a/hooks/get_task_request.md @@ -4,10 +4,10 @@ This hook is called when the observed client agent incquiry about a delegated ta This hook **must** be used before the observed agent sends the A2A-compliant message to server agent. #### 2. Method -[`tasks/get`](specification.md#48-a2a-protocol-methods) +`tasks/get` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | @@ -101,10 +101,10 @@ This hook is called when the observed server agent receives an incquiry message This hook **must** be used before the observed agent processes the A2A-compliant message. #### 2. Method -[`tasks/get`](specification.md#48-a2a-protocol-methods) +`tasks/get` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | diff --git a/docs/spec/instrument/a2a/hooks/resubscribe_to_task_request.md b/docs/spec/instrument/a2a/hooks/resubscribe_to_task_request.md index cd3723e..3687a30 100644 --- a/docs/spec/instrument/a2a/hooks/resubscribe_to_task_request.md +++ b/docs/spec/instrument/a2a/hooks/resubscribe_to_task_request.md @@ -4,10 +4,10 @@ This hook is called when the observed client agent resubscirbes to server agent' This hook **must** be used before the observed agent sends the A2A-compliant message to server agent. #### 2. Method -[`tasks/resubscribe`](specification.md#48-a2a-protocol-methods) +`tasks/resubscribe` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | @@ -97,10 +97,10 @@ This hook is called when the observed server agent receives a notification resub This hook **must** be used before the observed agent processes the A2A-compliant. #### 2. Method -[`tasks/resubscribe`](specification.md#48-a2a-protocol-methods) +`tasks/resubscribe` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | diff --git a/docs/spec/instrument/a2a/hooks/send_message_request.md b/docs/spec/instrument/a2a/hooks/send_message_request.md index a2a5da6..02b7f9b 100644 --- a/docs/spec/instrument/a2a/hooks/send_message_request.md +++ b/docs/spec/instrument/a2a/hooks/send_message_request.md @@ -5,10 +5,10 @@ This hook is called when the observed client agent sends a message to server age This hook **must** be used before the observed agent sends the A2A-compliant message to server agent. #### 2. Method -[`message/send`](specification.md#48-a2a-protocol-methods) +`message/send` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | @@ -119,10 +119,10 @@ This hook is called when the observed server agent received send message request This hook **must** be used before the observed agent processes the A2A-compliant message. #### 2. Method -[`message/send`](specification.md#48-a2a-protocol-methods) +`message/send` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | diff --git a/docs/spec/instrument/a2a/hooks/set_task_push_notification_config_request.md b/docs/spec/instrument/a2a/hooks/set_task_push_notification_config_request.md index 4c1b60e..6151ce4 100644 --- a/docs/spec/instrument/a2a/hooks/set_task_push_notification_config_request.md +++ b/docs/spec/instrument/a2a/hooks/set_task_push_notification_config_request.md @@ -4,10 +4,10 @@ This hook is called when the observed client agent sets or updates notification This hook **must** be used before the observed agent sends the A2A-compliant message to server agent. #### 2. Method -[`tasks/pushNotificationConfig/get`](specification.md#48-a2a-protocol-methods) +`tasks/pushNotificationConfig/get` #### 6.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | @@ -104,10 +104,10 @@ This hook is called when the observed server client agent receives a notificatio This hook **must** be used before the observed agent processes the A2A-compliant message. #### 2. Method -[`tasks/pushNotificationConfig/get`](specification.md#48-a2a-protocol-methods) +`tasks/pushNotificationConfig/get` #### 6.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | diff --git a/docs/spec/instrument/a2a/hooks/stream_message_request.md b/docs/spec/instrument/a2a/hooks/stream_message_request.md index d3e501e..d6009bb 100644 --- a/docs/spec/instrument/a2a/hooks/stream_message_request.md +++ b/docs/spec/instrument/a2a/hooks/stream_message_request.md @@ -4,11 +4,11 @@ This hook is called when the observed client agent sends a message to server age This hook **must** be used before the observed agent sends the A2A-compliant message to server agent. #### 2. Method -[`message/stream`](specification.md#48-a2a-protocol-methods) +`message/stream` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | @@ -143,11 +143,11 @@ This hook is called when the observed server agent receves stream message reques This hook **must** be used before the observed agent processes the A2A-compliant message. #### 2. Method -[`message/stream`](specification.md#48-a2a-protocol-methods) +`message/stream` #### 3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +The response is an [response envelope](../../specification.md#6-disposition-vocabulary) object. | Decision | Behavior | | :--------- | :---------- | diff --git a/docs/spec/instrument/extend_mcp.md b/docs/spec/instrument/extend_mcp.md index a1e4c16..5d8eccd 100644 --- a/docs/spec/instrument/extend_mcp.md +++ b/docs/spec/instrument/extend_mcp.md @@ -11,7 +11,7 @@ ACS extension for MCP is used as a **transport** for MCP communications between Securing MCP means securing outbound and inbound communications/messages from the agent (using MCP client) to the MCP server and vice versa.
#### To extend MCP protocol: -1. Agents using MCP ***must*** use ACS as a transport protocol to deliver MCP messages to the guardian agent using [MCP protocol hooks](hooks.md#mcp-protocol-hooks). +1. Agents using MCP ***must*** use ACS as a transport protocol to deliver MCP messages to the guardian agent using [`protocols/MCP/*`](hooks.md#protocolsmcp). 2. Agents using MCP ***must*** understand and enforce ACS responses. #### The following flow explains how this should be done: diff --git a/docs/spec/instrument/hooks.md b/docs/spec/instrument/hooks.md index 552b21b..e382ed2 100644 --- a/docs/spec/instrument/hooks.md +++ b/docs/spec/instrument/hooks.md @@ -1,635 +1,293 @@ -# Supported hooks +# Hooks + +ACS v0.1.0 defines 16 native `steps/*` hooks plus the wrapped `protocols/MCP/*` namespace, the Inspect-pillar `agbom/*` methods, and the `system/ping` liveness method. This page catalogs each hook: when it fires, the canonical schema, the disposition contract, and the audit-chain implications. + +The full per-hook payload schemas live under [`specification/v0.1.0/hooks/`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/). Common envelope rules — `request_id`, `timestamp`, `acs_version`, `metadata`, signature handling, replay protection — are documented in [Specification §3](./specification.md#3-wire-format) and [§10.3](./specification.md#103-replay-protection). + +## Overview + +| Hook | When it fires | Decision-eligible | Audit-chain | +|---|---|---|---| +| [`sessionStart`](#sessionstart) | Session initiation | Yes | Root entry | +| [`agentTrigger`](#agenttrigger) | Agent activation by event/schedule | Yes | Yes | +| [`turnStart`](#turnstart) | Beginning of an agent turn | Yes (most return ALLOW) | Yes | +| [`userMessage`](#usermessage) | User input received | Yes | Yes | +| [`agentResponse`](#agentresponse) | Agent output before user delivery | Yes | Yes | +| [`knowledgeRetrieval`](#knowledgeretrieval) | RAG / knowledge lookup | Yes | Yes | +| [`memoryContextRetrieval`](#memorycontextretrieval) | Memory read | Yes | Yes | +| [`memoryStore`](#memorystore) | Memory write | Yes | Yes | +| [`toolCallRequest`](#toolcallrequest) | Before tool execution | Yes | Yes | +| [`toolCallResult`](#toolcallresult) | After tool execution, before agent ingestion | Yes | Yes | +| [`preCompact`](#precompact) | Before context-window compaction | Yes | Yes | +| [`postCompact`](#postcompact) | After compaction; carries new summary | No (audit + lineage binding) | Yes | +| [`subagentStart`](#subagentstart) | In-process subagent spawned | Yes | Yes | +| [`subagentStop`](#subagentstop) | Subagent terminated | No | Yes | +| [`turnEnd`](#turnend) | End of an agent turn | No (audit) | Yes | +| [`sessionEnd`](#sessionend) | Session termination | No (audit finalization) | Yes | +| [`agbom/snapshot`](#agbomsnapshot) | Full AgBOM, once per session | Yes (banned components) | Yes | +| [`agbom/changed`](#agbomchanged) | Mid-session AgBOM mutation | Yes | Yes | +| [`system/ping`](#systemping) | Liveness probe | Always ALLOW | No | +| `protocols/MCP/*` | Wrapped MCP messages | Yes | Yes | + +`protocols/A2A/*` is reserved for v0.2. + +## Common envelope + +Every native hook uses the standard request envelope from [`request-envelope.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/request-envelope.json): + +```json +{ + "jsonrpc": "2.0", + "method": "steps/", + "id": "", + "params": { + "acs_version": "0.1.0", + "request_id": "", + "timestamp": "", + "tenant_id": "", + "metadata": { + "agent_id": "", + "session_id": "", + "turn_id": "" + }, + "payload": { /* hook-specific */ }, + "signature": { "algorithm": "...", "value": "...", "key_id": "..." } + } +} +``` -The supported hooks intervent the agent's workflow and interactions with the environment (see [Agent Environment Overview](../topics/core_concepts.md#agent-environment-overview)), to seamlessly expose the interaction data through the ACS standard. +The decision envelope shape is documented in [Specification §6](./specification.md#6-disposition-vocabulary) and [`response-envelope.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/response-envelope.json). -| Components | When | Native support | Protocols -|--|--|--|--| -| Trigger | On trigger received | [Details](#1-agent-trigger) | | -| API Tools, OS Tools | On tool call request | [Details](#2-tool-call-request) | [MCP](#10-mcp-outbound) | -| API Tools, OS Tools | On tool call completed | [Details](#3-tool-call-result) | [MCP](#11-mcp-inbound) | -| User | On user interaction received | [Details](#4-user-message) | | -| Memory | On memory context retrieved | [Details](#5-memory-context-retrieval) | | -| Memory | On memory store | [Details](#7-memory-store) | | -| Knowledge | On knowledge retrieved | [Details](#6-knowledge-retrieval) | | -| User | On agent response ready to send back to the user | [Details](#8-agent-response) | | -| Other Agents | On task delegation to other agent | | A2A([send](a2a/hooks/send_message_request.md), [stream](a2a/hooks/stream_message_request.md))| -| Other Agents | On task cancellation | | [A2A](a2a/hooks/cancel_request.md)| -| Other Agents | On task details / status inquiry | | [A2A](a2a/hooks/get_task_request.md)| -| Other Agents | On task notification config inquiry | | [A2A](a2a/hooks/get_notification_config_request.md)| -| Other Agents | On task notification config update | | [A2A](a2a/hooks/set_notification_config_request.md)| -| Other Agents | On task updates subscribe | | [A2A](a2a/hooks/resubscribe_to_task_request.md)| +--- +## sessionStart -## 1. Agent Trigger +Schema: [`hooks/session-start.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/session-start.json). -### 1.1. Description -This hook is called when the agent is triggered by an event, such as email or slack notifcations, recurrent schedule etc.
-This hook should be used **after** the content is extracted from the trigger and **before** the agent is triggered or activated. +Fires once per session, before any other `steps/*` hook for the same `session_id`. Establishes the audit chain root (`previous_hash: null`), session-level identity and policy bindings, and the initial `Intent` (when IBAC is the enforcement paradigm). -### 1.2. Method -[`steps/agentTrigger`](specification.md#41-stepsagenttrigger) +**Payload:** session-level identity descriptors, declared policy mode, `intent` (optional, with `parser_provenance`), platform context. -### 1.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +**Decision:** ALLOW / DENY. A Guardian MAY refuse a session whose identity, policy mode, or platform fails policy checks; this is the cleanest place to refuse before content enters. -| Decision | Behavior | -| :--------- | :---------- | -| `allow` | The agent should be triggered with the original extracted content from the trigger. | -| `deny` | The trigger should be blocked and agent should not be triggered. | -| `modify` | The agent should be triggered with the modified content found in `modifiedRequest` field. | +A deployment that does not emit `sessionStart` MAY allow the Guardian to implicitly initialize the chain at the first content-bearing hook, but this is discouraged because it leaves no place to attach session-level Intent before content enters. +--- -### 1.4. Example - ```json -{ - "jsonrpc": "2.0", - "method": "steps/agentTrigger", - "id": "ec3485a7-6e51-469f-8901-ae8538d6db9c", - "params": { - "trigger": { - "type": "autonomous", - "content": [ - { - "kind": "data", - "data": { - "to": "user@company.io", - "from": "no-reply@accounts.google.com", - "subject": "Security Alert", - "body": "We noticed a new sign-in to your Google Account on a Apple iPhone device. If this was you, you don't need to do anything. If not, we'll help you secure your account." - } - } - ], - "event": { - "type": "email", - "id": "b13e363f-1387-41ce-bff0-62ee518c60cf" - } - }, - "context": { - "agent": { - "id": "1c88ab7d-395f-449a-af51-6028f9e842ea", - "name": "Personal assistant", - "instructions": "You are very helpful agent. You manage my email box.", - "version": "9889", - "provider": { - "name": "OpenAI", - "url": "https://openai.com/" - } - }, - "session": { - "id": "e4368263-1797-48ac-9ca8-61a6b4ad9ea3" - }, - "turnId": "f128c460-241f-44a9-b4eb-5e5c4a2f56ea", - "stepId": "d87380ae-6b3b-454a-b911-0c1396e2ef68", - "timestamp": "2025-01-24T15:30:45.123Z", - } - } -} - ``` +## agentTrigger -## 2. Tool Call Request +Schema: [`hooks/agent-trigger.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/agent-trigger.json). -### 2.1. Description -This hook is called when the agent decides on calling a tool.
-This hook should be used **after** the inputs are extracted and **before** the tool is called. +Fires when the agent is activated by an event (email arrival, scheduled trigger, A2A inbound, etc.). For A2A-mediated delegation, `trigger_type: "a2a_inbound"` carries the originating peer identity; in-process subagent spawns use [`subagentStart`](#subagentstart) instead. -### 2.2. Method -[`steps/toolCallRequest`](specification.md#46-stepstoolcallrequest) +**Payload:** `trigger_type` (`autonomous`, `user_initiated`, `scheduled`, `a2a_inbound`, `local`), `trigger_event` (event id, source, payload), agent context. -### 2.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +**Decision:** ALLOW / DENY / MODIFY. Guardian MAY rewrite the trigger payload (e.g. redact PII) before activation. -| Decision | Behavior | -| :--------- | :---------- | -| `allow` | The tool should be called with extracted input parameters. | -| `deny` | The tool should not be called. It should be blocked. | -| `modify` | The tool should be called with the modified inputs found in `modifiedRequest` field. | +--- +## turnStart -### 2.4. Example - ```json -{ - "jsonrpc": "2.0", - "method": "steps/toolCallRequest", - "id": "13fa8d6f-8f9f-4d01-ba6b-db99d84d77de", - "params": { - "toolCallRequest": { - "executionId": "69dbf4c3-be33-4694-a9f0-8d3a824c5d5b", - "toolId": "c264f381-10cf-4403-bd11-383014c0fcc6", - "inputs": [ - { - "name": "phone_number", - "value": "+337-665-99-06" - }, - { - "name": "conent", - "value": "Urgent security alert from Google!!" - } - ] - }, - "reasoning": "Detected urgent email that needs the user's attention. I should use the send_sms tool to notify the user.", - "context": { - "agent": { - "id": "1c88ab7d-395f-449a-af51-6028f9e842ea", - "name": "Personal assistant", - "instructions": "You are very helpful agent. You manage my email box.", - "version": "9889", - "provider": { - "name": "OpenAI", - "url": "https://openai.com/" - } - }, - "session": { - "id": "e4368263-1797-48ac-9ca8-61a6b4ad9ea3" - }, - "turnId": "69ef57b8-3993-440d-9493-523914f3f149", - "stepId": "9263448a-186a-4c3b-abcf-443feb44a01e", - "timestamp": "2025-01-24T15:32:45.123Z", - } - } -} - ``` +Schema: [`hooks/turn-start.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/turn-start.json). -## 3. Tool Call Result +Lightweight hook marking the start of an agent turn. Many policies key on per-turn state — "deny consequential actions in any turn after a turn that retrieved untrusted data," "limit tool-call count per turn," "reset cumulative-taint at turn boundary." Without an explicit turn boundary, every Guardian rolls its own heuristic for inferring turn breaks (usually pairing `userMessage` with the next `agentResponse`), and the heuristics don't agree under auto-continuation, planning loops, and multi-step ReAct cycles. -### 3.1. Description -This hook is called when tool is completed.
-This hook should be used **before** the tool result is processed. +`turn_id` is added to the request envelope's `metadata` block and propagated onto every per-step ContextEntry between `turnStart` and `turnEnd`. AARM-style "no consequential action in N turns after taint" becomes computable from the audit chain in O(1) per check. -### 3.2. Method -[`steps/toolCallResult`](specification.md#46-stepstoolcallresult) +**Payload:** `turn_id`, `triggered_by` (`user_message`, `auto_continuation`, `agent_loop`, `subagent_return`), optional `parent_turn_id` for nested turns. -### 3.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +**Decision:** Decision-eligible — a Guardian MAY deny to block the turn from starting — but most deployments will return ALLOW and use the hook for state transitions in policy. -| Decision | Behavior | -| :--------- | :---------- | -| `allow` | The tool result should be processed by the agent. | -| `deny` | The tool result should not be further processed or used by the agent. | -| `modify` | The tool result should be processed with the modified inputs found in `modifiedRequest` field. | +--- +## userMessage -### 3.4. Example - ```json -{ - "jsonrpc": "2.0", - "method": "steps/toolCallResult", - "id": "9ba7f23b-6280-4f87-9d29-9ef82064f91e", - "params": { - "toolCallResult": { - "executionId": "69dbf4c3-be33-4694-a9f0-8d3a824c5d5b", - "result": { - "outputs": [], - "isError": false, - } - }, - "reasoning": "Sent the user an sms with to notify about the security alert using send_sms tool. My task is completed.", - "context": { - "agent": { - "id": "1c88ab7d-395f-449a-af51-6028f9e842ea", - "name": "Personal assistant", - "instructions": "You are very helpful agent. You manage my email box.", - "version": "9889", - "provider": { - "name": "OpenAI", - "url": "https://openai.com/" - } - }, - "session": { - "id": "e4368263-1797-48ac-9ca8-61a6b4ad9ea3" - }, - "turnId": "69ef57b8-3993-440d-9493-523914f3f149", - "stepId": "9263448a-186a-4c3b-abcf-443feb44a01e", - "timestamp": "2025-01-24T15:34:45.123Z", - } - } -} - ``` +Schema: [`hooks/user-message.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/user-message.json). -## 4. User Message +User input received, before reaching the agent. Provenance: `origin: user_input`. -### 4.1. Description -This hook is called when a user sends a prompt to the agent.
-This hook should be used **before** the user prompt reaches the agent.
+**Payload:** message content, optional citation/sources, user context. -### 4.2. Method -[`steps/message`](specification.md#45-stepsmessage)

-This method is used with [Agent Response](#8-agent-response) hook.
-For this hook `role` **MUST** be `user` (see example). +**Decision:** ALLOW / DENY / MODIFY. Guardian MAY redact content before delivery to the agent. +--- -### 4.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +## agentResponse -| Decision | Behavior | -| :--------- | :---------- | -| `allow` | The agent should be triggered with the original user prompt. | -| `deny` | The trigger should be blocked and agent should not be triggered. | -| `modify` | The agent should be triggered with the modified prompt found in `modifiedRequest` field. | +Schema: [`hooks/agent-response.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/agent-response.json). +Agent output, before reaching the user. Provenance: `origin: agent_generated` with `derived_from` set to whatever inputs the response is derived from. -### 4.4. Example - ```json -{ - "jsonrpc": "2.0", - "method": "steps/message", - "id": "55a8c2d7-0ea3-4cc7-b5e8-c859bf7a612f", - "params": { - "message": { - "role": "user", - "id": "a66c132e-a554-4dfc-8a47-2db66e13ef39", - "content": [ - { - "kind": "text", - "text": "What is the bank account of Acme Corp?" - } - ] - }, - "context": { - "agent": { - "id": "1c88ab7d-395f-449a-af51-6028f9e842ea", - "name": "Payments agent", - "instructions": "You are very helpful agent. You manage customers bank accounts and payments", - "version": "8878", - "provider": { - "name": "OpenAI", - "url": "https://openai.com/" - } - }, - "session": { - "id": "84c36ebb-83aa-4bc9-8670-7aba4cedc70f" - }, - "turnId": "f31ec273-9272-47dd-8ec4-8b2da695507e", - "stepId": "f3a357c4-2257-4cbe-ba16-e6fa2ca4e2ed", - "timestamp": "2025-01-24T15:30:45.123Z", - "user": { - "id": "8cc6e9bc-6ad5-4b95-8060-300915b1aaba", - "email": "user@company.io", - "organization": { - "id": "d8b0a63e-9a5d-4638-b5a3-4361ba067200", - "name": "Azura" - } - } - } - } -} - ``` -## 5. Memory Context Retrieval -### 5.1. Description -This hook is called when the agent retrieves content from the memory store such as conversation histroy.
-This hook should be used **after** memory store is retrieved and **before** it is attached to the context window.
+**Payload:** response content, optional sources/citations, agent reasoning. -### 5.2. Method -[`steps/memoryContextRetrieval`](specification.md#44-stepsmemorycontextretrieval)

+**Decision:** ALLOW / DENY / MODIFY. +--- -### 5.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +## knowledgeRetrieval -| Decision | Behavior | -| :--------- | :---------- | -| `allow` | The memory store should be attached to the context. | -| `deny` | The memory store should not be attached to the context. | -| `modify` | The memory store should be attached to the context with the modified content found in `modifiedRequest` field. | +Schema: [`hooks/knowledge-retrieval.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/knowledge-retrieval.json). -### 5.4. Example - ```json -{ - "jsonrpc": "2.0", - "method": "steps/memoryContextRetrieval", - "id": "5d5d6170-ffba-4520-9839-0da4c31b5497", - "params": { - "memory": [ - "[{\"role\":\"user\",\"message\":\"what is bank account of Continental Bank?\"},{\"role\":\"agent\",\"message\":\"Bank account of Continental Bank is 000456789123\"}]", - ], - "reasoning": "I might find these details from previous interactions. I need to look at the conversation history to decide.", - "context": { - "agent": { - "id": "1c88ab7d-395f-449a-af51-6028f9e842ea", - "name": "Payments agent", - "instructions": "You are very helpful agent. You manage customers bank accounts and payments", - "version": "8878", - "provider": { - "name": "OpenAI", - "url": "https://openai.com/" - } - }, - "session": { - "id": "84c36ebb-83aa-4bc9-8670-7aba4cedc70f" - }, - "turnId": "083db36a-5ba1-4d37-8c3f-ebc2ec23b96b", - "stepId": "491fef1e-992d-4503-aadb-e36c935fdeb2", - "timestamp": "2025-01-24T15:31:00.123Z", - "user": { - "id": "8cc6e9bc-6ad5-4b95-8060-300915b1aaba", - "email": "user@company.io", - "organization": { - "id": "d8b0a63e-9a5d-4638-b5a3-4361ba067200", - "name": "Azura" - } - } - } - } -} - ``` +Fires when the agent retrieves external knowledge (RAG, vector search, knowledge base lookup). Provenance: `origin: retrieved`, with `source_id` identifying the index or knowledge source. -## 6. Knowledge Retrieval +**Payload:** `query`, `keywords`, retrieved results (each with content, mime type, source id). -### 6.1. Description -This hook is called when the agent retrieve data from a knowledge source.
-This hook should be used **after** knowledge is retrieved and **before** it gets into the LLM to generate responses.
+**Decision:** ALLOW / DENY / MODIFY. Guardian MAY redact retrieved content before injection into the agent context. -### 6.2. Method -[`steps/knowledgeRetrieval`](specification.md#42-stepsknowledgeretrieval)

+--- +## memoryContextRetrieval -### 6.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +Schema: [`hooks/memory-context-retrieval.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/memory-context-retrieval.json). -| Decision | Behavior | -| :--------- | :---------- | -| `allow` | The retrieved data should be attached as is into the LLM context. | -| `deny` | The data retrieved should be blocked. ie the agent should not use or attach the retieved data to it's context. | -| `modify` | The agent should use the retierved data with the modified content found in `modifiedRequest` field. | +Memory read — long-term, session-scoped, or user-scoped — into the agent's working context. Provenance: `origin: retrieved`, `source_id` identifies the memory store. -### 6.4. Example - ```json -{ - "jsonrpc": "2.0", - "method": "steps/knowledgeRetrieval", - "id": "651aed43-4aca-4226-94fe-66fb77cd6c4a", - "params": { - "knowledgeStep": { - "query": "Bank account of Acme Corp", - "keywords": [ - "Bank", - "Account", - "Acme Corp" - ], - "results": [ - { - "id": "0a267158-7b44-452a-bba8-c1107bdf6128", - "content" :" - Account_ID,Account_Holder,Bank_Name,Account_Type,Routing_Number,Account_Number,Currency - BA-1001,Acme Corp,First National Bank,Checking,111000025,000123456789,USD - BA-1002,Globex Industries,Metro Credit Union,Savings,222000198,000987654321,USD - BA-1003,Initech LLC,Continental Bank,Checking,333000455,000456789123,EUR" - } - ] - }, - "reasoning": "I need to find a bank account. I expect this to be available in Bank Accounts.xlsx file.", - "context": { - "agent": { - "id": "1c88ab7d-395f-449a-af51-6028f9e842ea", - "name": "Payments agent", - "instructions": "You are very helpful agent. You manage customers bank accounts and payments", - "version": "8878", - "provider": { - "name": "OpenAI", - "url": "https://openai.com/" - } - }, - "session": { - "id": "84c36ebb-83aa-4bc9-8670-7aba4cedc70f" - }, - "turnId": "083db36a-5ba1-4d37-8c3f-ebc2ec23b96b", - "stepId": "234b6b2f-a2af-45d1-95b9-c16c13dca431", - "timestamp": "2025-01-24T15:31:45.123Z", - "user": { - "id": "8cc6e9bc-6ad5-4b95-8060-300915b1aaba", - "email": "user@company.io", - "organization": { - "id": "d8b0a63e-9a5d-4638-b5a3-4361ba067200", - "name": "Azura" - } - } - } - } -} - ``` +**Payload:** memory entries pulled into context. -## 7. Memory Store +**Decision:** ALLOW / DENY / MODIFY. -### 7.1. Description -This hook is called when the agent stores data into the memory store.
-This hook should be used **before** the memory store is updated.
+--- -### 7.2. Method -[`steps/memoryStore`](specification.md#43-stepsmemorystore)

+## memoryStore +Schema: [`hooks/memory-store.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/memory-store.json). -### 7.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +Memory write. The standard sink for cross-session influence; mediating it prevents memory poisoning. -| Decision | Behavior | -| :--------- | :---------- | -| `allow` | The memory store should be updated. | -| `deny` | The memory store should not be updated. | -| `modify` | The memory store should be updated with the modified content found in `modifiedRequest` field. | +**Payload:** entries to be written, target store, scope (`session`/`user`/`tenant`/`global`), TTL. +**Decision:** ALLOW / DENY / MODIFY. -### 7.4. Example - ```json -{ - "jsonrpc": "2.0", - "method": "steps/memoryStore", - "id": "797aad44-7cd1-43c3-a6ec-6536cd295ed9", - "params": { - "memory": [ - "[{\"role\":\"user\",\"message\":\"What is the bank account of Acme Corp?\"},{\"role\":\"agent\",\"message\":\"The bank account of Acme Corp is 000123456789\"}]", - ], - "reasoning": "I should memorize Acme Corp bank account details for future interactions.", - "context": { - "agent": { - "id": "1c88ab7d-395f-449a-af51-6028f9e842ea", - "name": "Payments agent", - "instructions": "You are very helpful agent. You manage customers bank accounts and payments", - "version": "8878", - "provider": { - "name": "OpenAI", - "url": "https://openai.com/" - } - }, - "session": { - "id": "84c36ebb-83aa-4bc9-8670-7aba4cedc70f" - }, - "turnId": "083db36a-5ba1-4d37-8c3f-ebc2ec23b96b", - "stepId": "ee33bce7-72b9-4ef7-a464-1f3f70ed7e06", - "timestamp": "2025-01-24T15:31:58.123Z", - "user": { - "id": "8cc6e9bc-6ad5-4b95-8060-300915b1aaba", - "email": "user@company.io", - "organization": { - "id": "d8b0a63e-9a5d-4638-b5a3-4361ba067200", - "name": "Azura" - } - } - } - } -} - ``` +--- -## 8. Agent Response +## toolCallRequest -### 8.1. Description -This hook is called when the agent sends back a response.
-This hook should be used **before** the agent response is sent to the user.
+Schema: [`hooks/tool-call-request.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/tool-call-request.json). -### 8.2. Method -[`steps/message`](specification.md#45-stepsmessage)

-This method is used with [User Message](#4-user-message) hook.
-For this hook `role` **MUST** be `agent` (see example). +Fires before tool execution. The central enforcement point for IBAC, FIDES, CaMeL, and AARM. Argument-level provenance attached to `ToolArgumentValue` lets Guardians reason about *which* arguments are tainted, not just whether the call is allowed at all. +**Payload:** `tool` (id, capability), `arguments` (each value carrying optional Provenance), agent reasoning. -### 8.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. +**Decision:** ALLOW / DENY / MODIFY / ASK / DEFER. The full disposition vocabulary applies — this is the hook where most paradigm composition happens. -| Decision | Behavior | -| :--------- | :---------- | -| `allow` | The agent response should be sent as is. | -| `deny` | The agent response should be blocked. Recommended to send a response indicating that the original response was blocked. | -| `modify` | The agent response should be sent back with the modified content found in `modifiedRequest` field. | +--- +## toolCallResult -### 8.4. Example - ```json -{ - "jsonrpc": "2.0", - "method": "steps/message", - "id": "716601aa-36eb-4720-ab08-c59b9321aecb", - "params": { - "message": { - "role": "agent", - "id": "a66c132e-a554-4dfc-8a47-2db66e13ef39", - "content": [ - { - "kind": "text", - "text": "The bank account of Acme Corp is 000123456789" - } - ] - }, - "citations": [ - { - "kind": "file", - "id": "0a267158-7b44-452a-bba8-c1107bdf6128", - "name": "Bank Accounts.xlsx" - } - ], - "reasoning": "Found Acme Corp bank account details in Bank Accounts.xlsx. I can respond to the user.", - "context": { - "agent": { - "id": "1c88ab7d-395f-449a-af51-6028f9e842ea", - "name": "Payments agent", - "instructions": "You are very helpful agent. You manage customers bank accounts and payments", - "version": "8878", - "provider": { - "name": "OpenAI", - "url": "https://openai.com/" - } - }, - "session": { - "id": "84c36ebb-83aa-4bc9-8670-7aba4cedc70f" - }, - "turnId": "083db36a-5ba1-4d37-8c3f-ebc2ec23b96b", - "stepId": "fdee9786-1754-4c87-962c-a1ed02918b99", - "timestamp": "2025-01-24T15:33:45.123Z", - "user": { - "id": "8cc6e9bc-6ad5-4b95-8060-300915b1aaba", - "email": "user@company.io", - "organization": { - "id": "d8b0a63e-9a5d-4638-b5a3-4361ba067200", - "name": "Azura" - } - } - } - } -} - ``` - -# MCP protocol hooks -For detailed explanation on how to extend MCP please refer to [extend_mcp.md](extend_mcp.md) - -## 11. MCP Outbound - -### 11.1. Description -This hook is called when the agent communicate with remote MCP servers via MCP protocol.
-This hook should be used **before** the agent sends MCP-compliant message to the remote MCP server.
- -### 11.2. Method -[`protocols/MCP`](specification.md#410-protocolsmcp)

- - -### 11.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. - -| Decision | Behavior | -| :--------- | :---------- | -| `allow` | The MCP message should be sent to the remote server as is. | -| `deny` | The MCP communication should be blocked. The message should not be sent to the remote MCP server. | -| `modify` | The MCP message should be sent to the remote server with the modified content found in `modifiedRequest` field. | - - -### 11.4. Example - ```json - { - "jsonrpc": "2.0", - "id": "13fd5c5c-2e82-47db-ac4b-227fffd6683a", - "method": "protocols/MCP", - "params": { - "jsonrpc": "2.0", - "id": "15275b01-b6dc-4fa5-9f17-6a949c72de3c", - "method": "tools/call", - "params": { - "arguments": { - "specialty": "Family Medicine", - "datetime": "2025-01-24T15:30:45.123Z", - "City": "Berlin" - }, - "name": "get_appointment_slots" - } - } - } - ``` - -## 12. MCP Inbound - -### 12.1. Description -This hook is called when the agent received a message from MCP remote server.
-This hook should be used **before** the agent processes the MCP received message from the remote MCP server.
- -### 12.2. Method -[`protocols/MCP`](specification.md#410-protocolsmcp)

- - -### 12.3. Reponse -The response is an [`ACSSuccessResponse`](specification.md#51-acssuccessresponse-object) object. - -| Decision | Behavior | -| :--------- | :---------- | -| `allow` | The MCP message should be processed by the agent as is. | -| `deny` | The MCP message should not be processed by the agent. | -| `modify` | The MCP message should be processed by the agent with the modified content found in `modifiedRequest` field. | - - -### 12.4. Example - ```json - { - "jsonrpc": "2.0", - "id": "e1c93383-e6ca-433c-999d-85cbca53a172", - "method": "protocols/MCP", - "params": { - "jsonrpc": "2.0", - "id": "15275b01-b6dc-4fa5-9f17-6a949c72de3c", - "result": { - "content": " - {\"specialty\":\"Family Medicine\",\"city\":\"Berlin\",\"requested_datetime\":\"2025-01-24T15:30:45.123Z\" - \"time_zone\":\"Europe/Berlin\",\"slots\":[{\"slot_id\":\"96e3e9e4-019d-4c2a-8a62-0f2f725882f9\" - \"start\":\"2025-01-24T16:00:00+01:00\",\"end\":\"2025-01-24T16:20:00+01:00\",\"doctor_name\":\"Dr. Anna Schmidt\" - \"clinic_name\":\"HealthyLife Praxis\"},{\"slot_id\":\"4b5d3fc7-0b4d-4376-bd2f-2f92fe7f32d2\" - \"start\":\"2025-01-24T16:30:00+01:00\",\"end\":\"2025-01-24T16:50:00+01:00\",\"doctor_name\":\"Dr. Lukas Becker\" - \"clinic_name\":\"Kreuzberg Family Clinic\"},{\"slot_id\":\"18d97122-aba5-4f46-92d5-9bdd1e14cf2b\" - \"start\":\"2025-01-24T17:10:00+01:00\",\"end\":\"2025-01-24T17:30:00+01:00\",\"doctor_name\":\"Dr. Jana Meyer\" - \"clinic_name\":\"Prenzlauer Care Center\"}]}" - } - } - } - ``` \ No newline at end of file +Schema: [`hooks/tool-call-result.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/tool-call-result.json). + +Fires after tool execution, before the result is ingested into the agent. Provenance: `origin: tool_output`, with `derived_from` set to the originating `toolCallRequest`'s provenance ids when the tool's output is data-derived. + +**Payload:** `execution_id` (correlated to the request), outputs, exit status, duration. + +**Decision:** ALLOW / DENY / MODIFY. + +--- + +## preCompact + +Schema: [`hooks/pre-compact.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/pre-compact.json). + +Fires before context-window compaction. Compaction is the chokepoint where provenance can be laundered: when the runtime LLM compresses a long context window into a summary, the post-compaction text is new `agent_generated` content whose `derived_from` lineage spans every untrusted item that was in the pre-compaction context. Without an explicit hook, the framework has no clean place to attach the rule that *the compacted summary's lineage is the union of all summarized entries' lineage*. AARM cumulative-context tracking breaks across compaction without it, FIDES's monotonicity claim is unverifiable, and Guardians cannot enforce "don't compact across a trust boundary" policies. + +**Payload:** `entries_to_compact` (array of `step_id`s that will be summarized), pre-compaction `provenance_summary` (so the Guardian can see what it's about to lose), `triggered_by` (`size_threshold`, `manual`, `agent_initiated`). + +**Decision:** Decision-eligible. Guardian MAY return DENY to block compaction (e.g. because deployment policy disallows compacting after `untrusted` data has entered until a trusted re-grounding occurs). + +--- + +## postCompact + +Schema: [`hooks/post-compact.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/post-compact.json). + +Fires after compaction. Audit + provenance-binding hook. + +**Payload:** resulting `summary` content with `provenance` whose `origin` MUST be `agent_generated` and whose `derived_from` MUST equal the union of `provenance_id`s of every entry in `entries_compacted`. When the deployment populates `trust` on the wire, `trust` MUST equal the minimum trust of those entries (the standard monotonicity rule applied to summarization). The framework — not the LLM — populates `derived_from`. + +**Decision:** Not decision-eligible — compaction has already occurred. A Guardian MAY return MODIFY (rewrite the summary, e.g. to redact a region the policy can't compact), but MAY NOT return DENY. The audit chain MUST record the post-compact state regardless. + +--- + +## subagentStart + +Schema: [`hooks/subagent-start.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/subagent-start.json). + +In-process delegation — a parent agent spawning a subagent within the same runtime, with no A2A boundary crossed — needs an explicit lifecycle event. A2A-mediated delegation already flows through [`agentTrigger`](#agenttrigger) with `trigger_type: a2a_inbound` on the subagent's side; `subagentStart` is for the same-runtime case. + +This matters for IBAC: when a subagent spawns, the audit chain must record whether it inherits the parent's `Intent.parsed`, gets a derived intent, or starts fresh — and whether subsequent operations are checked against the parent's, the subagent's, or both Intents. The composition case (IBAC outer + CaMeL inner) is precisely the case this hook handles. + +**Payload:** `subagent_session_id` (a fresh `session_id` distinct from the envelope's parent `session_id`), `parent_session_id`, `parent_step_id` (the originating step that triggered the spawn), `intent_derivation` (`inherit_full` / `inherit_subset` / `derived_from_parent` / `fresh`), `subagent_intent` (the new Intent for the subagent, with `parser_provenance`). + +**Decision:** Decision-eligible. Guardian MAY DENY — refuse the subagent spawn, e.g. because the `intent_derivation` would grant capabilities the parent's `Intent.parsed` does not authorize. + +Each subagent has its own SessionContext and audit chain; the parent–child relation is captured in the `subagentStart` payload. + +--- + +## subagentStop + +Schema: [`hooks/subagent-stop.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/subagent-stop.json). + +**Payload:** `subagent_session_id`, `outcome` (`completed`, `failed`, `cancelled`), the subagent's `final_chain_hash`, optional `summary` of what was returned to the parent. The summary's `provenance` follows the standard monotonicity rule. + +**Decision:** Audit only. + +--- + +## turnEnd + +Schema: [`hooks/turn-end.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/turn-end.json). + +**Payload:** `turn_id`, `outcome` (`completed`, `deferred`, `error`), `step_count`, optional `summary`. + +**Decision:** Not decision-eligible — the turn has already happened. + +--- + +## sessionEnd + +Schema: [`hooks/session-end.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/session-end.json). + +Session termination, audit finalization. The Guardian seals the chain at this point. + +**Payload:** `session_reason` (`completed`, `cancelled`, `error`, `timeout`), final aggregates. + +**Decision:** Audit only. + +--- + +## agbom/snapshot + +Schema: [`hooks/agbom-snapshot.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/agbom-snapshot.json). + +Inspect-pillar method. Fires once per session, after `sessionStart` and before any content-bearing hook, and again after handshake-renegotiation. Carries the full AgBOM (the Observed Agent's component graph: models, MCP servers, A2A peers, tools, knowledge sources, memory stores, agent capabilities). + +**Decision:** Normally ALLOW. Guardian MAY DENY to refuse a session whose component graph contains a banned model, tool, or peer. + +See the [Inspect pillar](../inspect/README.md) for the full AgBOM schema and serialization mapping. + +--- + +## agbom/changed + +Schema: [`hooks/agbom-changed.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/agbom-changed.json). + +Inspect-pillar method. Fires whenever a component is added, removed, or version-changed mid-session. Carries either a full snapshot or a diff (`added[]`, `removed[]`, `changed[]`). + +**Decision:** ALLOW / DENY. Guardian MAY DENY to block a hot-swap; otherwise audited. + +`agbom/changed` is part of the **ACS-Inspect-Dynamic** profile extension. Deployments claiming the base **ACS-Inspect** profile (snapshot only) are not required to emit it. + +--- + +## system/ping + +Schema: [`hooks/system-ping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/system-ping.json). See [Specification §13](./specification.md#13-liveness-system-methods). + +Liveness probe. Always returns `decision: "allow"` regardless of policy, signature, or session state. NOT written into SessionContext. NOT subject to signature requirements even when the session otherwise requires signatures. + +--- + +## protocols/MCP/\* + +See [Extending MCP](./extend_mcp.md). Wrapped MCP messages flow through `protocols/MCP/*` (e.g. `protocols/MCP/tools/call`). The wrapped methods carry the underlying MCP message intact and apply the standard ACS envelope, decision contract, and audit-chain rules on top. diff --git a/docs/spec/instrument/specification.md b/docs/spec/instrument/specification.md index 0a2e916..a80298a 100644 --- a/docs/spec/instrument/specification.md +++ b/docs/spec/instrument/specification.md @@ -1,784 +1,364 @@ -# ACS Specification +# ACS v0.1.0 — Instrument Specification **Version:** `0.1.0` -## 1. Core concepts -- **Guardian Agent**: An agent that monitors other agents behavior for anomalous and risky decisions. -- **Agent**: An agent that implements ACS-compliant HTTP endpoints, sending data and providing visibility into its plans, reasoning and context. Also understands ACS responses and enforces results. -- **Session**: A session is a scoped unit of interaction, starting when an agent is activated (by user or environment) and ending when the task or interaction is complete. The session consists of turns, where turn is a single end to end loop between user and agent. -- **Step**: A step is a single unit of action or decision taken by the agent as part of its reasoning or execution process. It can be a user message, tool/function call, memory operation (retrieve or store context), knowledge retrieval etc. -- **A2A Message**: A2A-protocol message captured between agents communication. -- **MCP Message**: MCP-protocol message captured between mcp client and mcp server communication. -- **User**: The user involved in agent/user interaction. +The Instrument pillar covers real-time interception, evaluation, and enforcement of agent behavior. It defines the wire format, the capability-negotiation handshake, the hook taxonomy, the disposition vocabulary, the SessionContext and Intent model, replay protection, and signature semantics. Trace (event emission) and Inspect (AgBOM) are co-equal v0.1.0 pillars covered in their own pages. -## 2. Transport and Format +A deployment that implements **ACS-Core** (this section's mandatory baseline) is v0.1.0-conformant. Additional capabilities — Trace event emission, AgBOM serialization, field-level provenance, cryptographic signatures, strengthened audit chains — are organized as **conformance profiles** (see [Conformance](../conformance.md)). -### 2.1. Transport Protocol +## 1. Design Principles -- ACS communication **MUST** occur over **HTTP(S)**. +1. **Opinionated on contract, permissive on implementation.** The wire format and semantics are locked. Engines, crypto suites, OS, and transport are deployment-owned. +2. **The agent MUST NOT have knowledge of hooks.** Provenance fields, when emitted, MUST be populated outside the LLM's output path. +3. **Facts on the wire; classification in policy.** Provenance fields (`origin`, `source_id`, `derived_from`) are populated by deterministic framework code at channel boundaries, never by the LLM. v0.1 keeps trust *classification* off the mandatory wire surface: Guardians derive trust from `origin` + `source_id` against local policy. The wire format reserves an OPTIONAL `trust` enum for vendor implementations that elect to carry the classification in the envelope; when populated, the monotonicity rule applies (§7). +4. **Three pillars, tiered conformance.** Instrument, Trace, and Inspect are all defined in v0.1.0. None is deferred. ACS-Core is the mandatory baseline; Trace, Inspect, Provenance, Crypto, and Audit are normative profiles deployments advertise via handshake negotiation. -### 2.2. Data Format +## 2. Architecture -ACS uses **[JSON-RPC 2.0](https://www.jsonrpc.org/specification)** as the payload format for all requests and responses +Two parties on the wire: -- Agent requests and guardian agent responses **MUST** adhere to the JSON-RPC 2.0 specification. -- The `Content-Type` header for HTTP requests and responses containing JSON-RPC payloads **MUST** be `application/json`. +- **Observed Agent** — the LLM-backed system being monitored. Implements ACS endpoints (or the stdio analog) and sends hook traffic to the Guardian. +- **Guardian Agent** — the policy enforcement point. Two internal layers: + - **Deterministic layer** (Cedar/Rego): always runs first. + - **Agent layer** (LLM): invoked only when the deterministic layer's chain config delegates (`*`, `on_ask`, or pattern-based). +### 2.1 End-to-end flow -## 3. Standard Data Objects -These objects define the structure of data exchanged within the JSON-RPC methods of ACS. +``` +Observed Agent Guardian Agent +══════════════ ══════════════ + │ │ + │ 1. Hook fires; framework builds JSON-RPC │ + │ 2. Send (HTTP POST or stdio write) │ + ├──────────────────────────────────────────────>│ + │ │ 3. Validate envelope, signature, replay + │ │ 4. Load SessionContext, Intent + │ │ 5. Deterministic layer evaluates + │ │ 6. If delegated, agent layer reasons + │ │ 7. Build decision envelope + │ 8. Receive response │ + │<──────────────────────────────────────────────┤ + │ 9. Enforce decision (allow/deny/modify/ask/defer) + │ 10. Audit entry written to SessionContext +``` +A worked example appears in [ACS in Action](../../topics/ACS_in_action_example.md). -### 3.1. `Agent` Object +## 3. Wire Format +| Element | Choice | +|---|---| +| Envelope | JSON-RPC 2.0, with top-level `acs_version` | +| Transports | HTTP(S) and stdio (Content-Length framing, UTF-8) | +| Method namespaces | `steps/*`, `protocols/A2A/*`, `protocols/MCP/*`, `agbom/*`, `system/*`, `handshake/*`, `trace/*` (reserved) | +| Wrapped methods | `protocols/MCP/*` is the canonical v0.1 namespace for MCP wrapping (e.g. `protocols/MCP/tools/call`). The `wrapped:` prefix is an alternative explicit-version form for deployments that pin a specific protocol version on the wire (e.g. `wrapped:mcp-2025-06-18/tools/call`). Both are valid; `protocols/*` is preferred. A2A wrapping deferred to v0.2. | +| Error code range | `-32000` to `-32099` reserved for ACS | +| Response shape | Discriminated union: `{ "type": "final", ... }` | +| Forward compat | Accept `X.Y.Z` matching major version; ignore unknown fields | -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | `string` | Yes | Human-readable name of the agent. | -| `id` | `string` | Yes | Id of the agent. | -| `description` | `string` | No | Human-readable description. | -| `instructions` | `string` | Yes | Agent internal instrucions, known as system prompt. | -| `version` | `string` | Yes | Agent version string. | -| `provider` | [`AgentProvider`](#311-agentprovider-object) | Yes | Information about the agent's provider. | -| `identity` | [`AgentIdentity`](#318-agentidentity-object) | Yes | Identity of the agent. | -| `model` | [`Model`](#312-model-object) | No | Agent's underlying LLM. | -| `tools` | [`ToolDefinition`](#32-tooldefinition-object)[] | No | Available tools. | -| `mcpServers` | [`MCPServer`](#314-mcpserver-object)[] | No | Available MCP servers. | -| `resources` | [`Resource`](#315-resource-object)[] | No | Available resources. | -| `organization` | [`Organization`](#311-organization-object) | No | Organization / entity that agent belongs to. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the agent. | +Streaming and notifications are not supported in v0.1.0. Batching is permitted as standard JSON-RPC 2.0 — Guardians SHOULD accept array-shaped requests and return an array of correlated responses — but ACS does not add atomicity, ordering, or cross-request dependency semantics in v0.1. Each request in a batch is evaluated independently, in declared order, with each carrying its own `request_id` and (if signed) its own signature. A Guardian that does not support batching MUST return `-32600 Invalid Request` for array-shaped inputs so the Observed Agent can fall back to sequential requests. +The full envelope schemas are [`request-envelope.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/request-envelope.json) and [`response-envelope.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/response-envelope.json). +## 4. Capability Negotiation Handshake -#### 3.1.1. `AgentProvider` Object +Required at session start, before any hook traffic. Wire method: `handshake/hello`. Schema: [`handshake.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/handshake.json) (`$defs/ClientHello` and `$defs/ServerHello`). -Information about the organization or entity providing the agent. +**Observed Agent → Guardian Agent (ClientHello):** `acs_versions_supported`, `methods_implemented`, `transports_supported`, `max_payload_size_bytes`, `provenance_producer`, `wrapped_protocols`, `profiles_supported` (conformance profiles the client implements; see [Conformance](../conformance.md)). +**Guardian Agent → Observed Agent (ServerHello):** `negotiated_version`, `methods_evaluated`, `selected_transport`, `signature_algorithms_supported`, `timeout_config` (default and per-method), `approver_types_supported`, `policy_requires_provenance`, `agbom_serializations_supported` (Inspect-pillar serialization formats the Guardian renders on request), `trace_emission` (whether the Guardian emits OTel and/or OCSF for the Trace pillar, plus optional OTLP collector endpoint), `profiles_accepted`. -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | `string` | Yes | Human-readable name of the agent provider. | -| `url` | `string` | Yes | URL for the provider's website/contact. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the agent provider. | +Version mismatch terminates with `UNSUPPORTED_VERSION`. Unknown fields MUST be ignored. If the client declares `provenance_producer: "none"` and the Guardian's `policy_requires_provenance` is true, the Guardian MUST refuse the session at handshake time rather than silently degrading enforcement. -#### 3.1.2. `Model` Object +## 5. Hook Taxonomy -Information about LLM associated with the agent. +Hook details, payloads, and per-hook examples are catalogued on the [Hooks](./hooks.md) page. The native `steps/*` set is: +| # | Method | Trigger | +|---|---|---| +| 1 | `sessionStart` | Session initiation, before any other `steps/*` hook for the `session_id` | +| 2 | `agentTrigger` | Agent activation | +| 3 | `turnStart` | Beginning of an agent turn | +| 4 | `userMessage` | User input received | +| 5 | `agentResponse` | Agent output before reaching the user | +| 6 | `knowledgeRetrieval` | RAG / knowledge lookup | +| 7 | `memoryContextRetrieval` | Memory read | +| 8 | `memoryStore` | Memory write | +| 9 | `toolCallRequest` | Before tool execution | +| 10 | `toolCallResult` | After tool execution, before agent ingestion | +| 11 | `preCompact` | Before context-window compaction; decision-eligible | +| 12 | `postCompact` | After compaction; carries the new summary's payload and provenance | +| 13 | `subagentStart` | A subagent is spawned (in-process delegation) | +| 14 | `subagentStop` | A subagent has terminated | +| 15 | `turnEnd` | End of an agent turn | +| 16 | `sessionEnd` | Session termination, audit finalization | -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | `string` | Yes | Human-readable name of the model (LLM). | -| `id` | `string` | Yes | Id of the model (LLM). | -| `provider` | [`LlmProvider`](#313-llmprovider-object) | Yes | The LLM provider. | -| `maxTokens` | `integer` | No | Maximum number of tokens the model is allowed to generate in the response. | -| `contextWindow` | `integer` | No | Total number of tokens (input + output) the model can handle in one request. | -| `stopSequences` | `string`[] | No | List of tokens that will stop the generation early (e.g., ["User:", "Agent:"]). | -| `defaultParams` | `object` | No | Default model parameters, such as `temperature`, `topK`, etc. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the model. | +Wrapped: `protocols/MCP/*` (specified in v0.1; see [Extending MCP](./extend_mcp.md)). The `protocols/A2A/*` namespace is reserved; wrapping specification deferred to v0.2. +Inspect-pillar methods (`agbom/*`): `agbom/snapshot` and `agbom/changed` (see [Inspect](../inspect/README.md)). System methods (`system/*`): `system/ping` (§13). These are not Instrument hooks — they're the wire surface for Inspect and transport-control — but they share the request-envelope shape, and `agbom/*` participates in the SessionContext audit chain. -#### 3.1.3. `LlmProvider` Object +## 6. Disposition Vocabulary -Information about the organization or entity providing the LLM. +| Disposition | Meaning | Required fields | +|---|---|---| +| `ALLOW` | Proceed | none (`reasoning` RECOMMENDED when user-visible audit trails are expected) | +| `DENY` | Block | `reasoning` | +| `MODIFY` | Proceed with changes (covers redaction via `modifications.redactions`) | `reasoning`, `modifications` | +| `ASK` | Pause and request approval (substituted with `DEFER` or `DENY` for approver-incapable clients — see [§9.2](#92-approver-incapable-clients-normative)) | `reasoning`, `ask_details` | +| `DEFER` | Verdict not yet reachable | `reasoning`, `defer_details` | +DEFER reasons: `insufficient_context`, `conflicting_policies`, `low_confidence`, `pending_dependency`. DEFER MUST include `resolution_method`, `resolution_timeout_ms`, and `timeout_decision` (default `deny`). Cascading deferrals MUST be bounded per session. -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | `string` | Yes | Human-readable name of the LLM provider. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the LLM provider. | +### 6.1 Decision result fields -#### 3.1.4. `MCPServer` Object +The decision envelope ([`response-envelope.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/response-envelope.json)) carries a fixed set of fields that compose to support audit, observability, and cross-paradigm enforcement: -Information about the available MCP servers. +| Field | Required | Purpose | +|---|---|---| +| `decision` | yes | The verdict, one of the five dispositions above. | +| `reasoning` | conditional | Single human-renderable explanation. Serves both end-user display and audit/agent-internal consumption; deployments wanting different text per audience SHOULD compose them client-side from `reasoning` + `policy_data` + `reason_codes`. | +| `policy_references` | no | Array of `{policy_id, policy_name, rule_id}` — the rules that fired. A single decision MAY cite multiple entries when several paradigms reject the same action; audit replay walks the list to reconstruct contributions. | +| `reason_codes` | no | Array of machine-readable categorization strings. Free vocabulary in v0.1. UIs and meta-policies SHOULD switch on these rather than parsing reasoning text or rule IDs. | +| `policy_data` | no | Free-form structured payload for paradigm- or policy-specific facts. When multiple paradigms fire, conventionally keyed by paradigm name (`{ "ibac": {...}, "fides": {...}, "aarm": {...} }`). | +| `cited_provenance_ids` | no | Array of `provenance_id`s whose facts drove this decision. Standard top-level surface for "which provenance objects mattered". | +| `modifications` / `ask_details` / `defer_details` | conditional | Disposition-specific payloads. | +| `metadata` | no | ACS-defined evaluator/observability metadata: `evaluator` (`deterministic`/`agent`/`composite`), `evaluator_version`, `evaluation_duration_ms`, `model_id` (required when `evaluator` is `agent` or `composite`), `confidence`. NOT for policy-emitted facts — those go in `policy_data`. | +### 6.2 Paradigm composition -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | `string` | Yes | Name of the MCP server. | -| `version` | `string` | Yes | Version of the MCP server. | -| `url` | `string` | Yes | URL of the MCP server. | +These fields support the v0.1 paradigm targets (FIDES, CaMeL, AARM-style cumulative-context, IBAC) without per-paradigm wire extensions. A FIDES P-T denial cites the violating lineage in `cited_provenance_ids` and exposes the violating-argument path in `policy_data`. An IBAC intent-mismatch DEFER routes through `defer_details` while exposing the requested capability and closest `Intent.parsed` match in `policy_data`. An AARM cumulative-context denial cites `earliest_untrusted_step_id` in `cited_provenance_ids` and reproduces the relevant lookback state in `policy_data`. When a deployment composes paradigms — e.g., IBAC outer + FIDES inner across an A2A boundary — a single decision MAY cite all of them: `policy_references` with one entry per paradigm, `reason_codes` with one or more codes per paradigm, `policy_data` keyed by paradigm. -#### 3.1.5. `Resource` Object +## 7. Provenance -Information about the available resources. +MAY be attached to data-bearing fields (`Message.content`, `KnowledgeRetrievalResult`, `ToolCallResult.outputs`, `ToolArgumentValue`, A2A payload). Field-level attachment is OPTIONAL — paradigms that do not require information-flow tracking (e.g. pure IBAC) can omit it. When a Provenance object is emitted, all required fields MUST be populated. Schema: [`provenance.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/provenance.json). +| Field | Required | Type | Notes | +|---|---|---|---| +| `provenance_id` | yes | string | Unique within session | +| `origin` | yes | enum | `user_input`, `system`, `tool_output`, `retrieved`, `agent_generated`, `a2a_inbound`, `external` | +| `source_id` | no | string | Identifier within origin | +| `derived_from` | no | string[] | Lineage: array of `provenance_id`s | -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | `string` | Yes | Name of the resource. | -| `id` | `string` | Yes | Id of the resource. | -| `description` | `string` | No | Resource description. | -| `content` | `string` | Yes | Resource content. | -| `mimeType` | `string` | No | [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) (e.g., text/plain, image/png). Strongly recommended. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the resource. | +### 7.1 Optional `trust` enum -### 3.2. `ToolDefinition` Object +The wire format reserves an OPTIONAL `trust` enum (`trusted`, `untrusted`, `unknown`) so vendor Guardian implementations can carry channel classification in the envelope rather than derive it in policy. v0.1 does not require Guardians to populate it. The expected v0.1 default is for Guardians to derive trust from `origin` + `source_id` against local policy: it keeps the wire format minimal, avoids creating labeled-trusted regions that downstream code stops scrutinizing, and prevents optional-field defaults from ossifying into a de-facto security model. Reserving the field on the wire preserves the option to populate it in a future version (or in a vendor extension today) without a wire-format break. -Describes the tool schema used by the agent. +When a deployment **does** populate `trust`: +- The framework — not the LLM — MUST attach the label, deterministically, based on which channel data crossed. `trust` is never a content judgment and never a producer claim. +- For data with `origin: agent_generated`, the framework MUST compute `trust` as the minimum trust of the entries in `derived_from` (monotonicity rule). No amount of LLM processing launders untrusted data into trusted data. +- Receivers (especially across A2A or multi-Guardian boundaries) MUST treat the field as a hint and re-derive trust against local policy keyed off `origin` + `source_id` rather than honor a remote-asserted label at face value. -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | `string` | Yes | Tool name. | -| `id` | `string` | Yes | Tool Id. | -| `description` | `string` | No | Tool description. Usually used by the LLM to determine on the tool call request. | -| `type` | `string` | Yes | The type of the tool. Such as function_call, api_request etc. | -| `arguments` | [`ToolArgumentDefinition`](#321-toolargumentdefinition-object)[] \| `null` | Yes | Array of tool arguments. Can be null if the tool has no arguments. | -| `outputs` | [`ToolOutputDefinition`](#322-tooloutputdefinition-object)[] \| `null` | Yes | Array of tool outputs. Can be null if the tool has no outputs. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the LLM provider. | +### 7.2 Default channel-to-trust mapping +Used both by Guardians that derive trust in policy and by vendor implementations that populate `trust` on the wire. Deployments MAY override in policy but SHOULD record overrides in audit metadata. -#### 3.2.1. `ToolArgumentDefinition` Object +| Origin | Default trust | +|---|---| +| `user_input` | `trusted` | +| `system` | `trusted` | +| `tool_output` | `untrusted` | +| `retrieved` | `untrusted` | +| `a2a_inbound` | `untrusted` | +| `external` | `untrusted` | +| `agent_generated` | minimum trust of `derived_from` lineage | -Describes the tool argument schema. +Provenance MUST be populated by deterministic code outside the LLM's output path. Implementations MUST NOT instruct the LLM to produce it. The agent declares `provenance_producer: "framework" | "llm" | "none"` in the handshake; a `none` producer emits no Provenance objects, and Guardians whose policies require Provenance MUST refuse the session at handshake time rather than silently degrading enforcement. +## 8. SessionContext and Intent -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | `string` | Yes | Argument name. | -| `id` | `string` | No | Argument Id. This should be used when tool argument can be defined and accessed by Id. | -| `description` | `string` | No | Argument description. Usually used by the LLM to determine on argument value. | -| `type` | `string` | No | The type of the argument. Allowed values are: `string`, `number`, `boolean`, `object`, `array`, `null`. | -| `mimeType` | `string` \| `null` | No | [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) (e.g., text/plain, image/png). Strongly recommended. | -| `required` | `boolean` | Yes | Whether the tool argument is required. | +State the Guardian Agent maintains across a session. Lives only on the Guardian Agent. The Observed Agent sends only `session_id` and an optional `chain_hash` for verification. -#### 3.2.2. `ToolOutputDefinition` Object +The chain root is established by the first ContextEntry the Guardian writes for a `session_id`, with `previous_hash: null`. This entry is normally produced by `sessionStart`; deployments that do not emit `sessionStart` MAY allow the Guardian to implicitly initialize the chain at the first content-bearing hook, but this is discouraged because it leaves no place to attach session-level identity, policy, or Intent before content enters. -Describes the tool output schema. +**SessionContext:** `session_id`, `chain_hash` (rolling SHA-256), `entries` (append-only `ContextEntry`), `provenance_summary`, `intent` (optional). +The SessionContext container is intentionally not schematized in v0.1. The wire-visible commitment (`chain_hash`) and the structurally-interesting children (`ContextEntry`, `ProvenanceSummary`, `Intent`) are each defined as portable schemas with normative computation rules; the container that holds them is server-side state and remains implementation-defined. Cross-Guardian interoperability is achieved through the standardized wire artifacts, not through a standardized container format. -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | `string` | Yes | Output name. This should be used when tool output can be defined and accessed by Id. | -| `id` | `string` | No | Output Id. This should be used when tool output can be defined and accessed by Id. | -| `description` | `string` | No | Output description. Usually used by the LLM to determine on output value. | -| `type` | `string` | No | The type of the argument. Allowed values are: `string`, `number`, `boolean`, `object`, `array`, `null`. | -| `mimeType` | `string` \| `null` | No | [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) (e.g., text/plain, image/png). Strongly recommended. | +### 8.1 ContextEntry +Schema: [`context-entry.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/context-entry.json). Append-only entry in the audit chain. -### 3.3. `Message` Object +- **Required:** `entry_id`, `step_id`, `step_type`, `entry_hash`. +- **SHOULD:** `request_hash` (lowercase-hex SHA-256 of the JCS-canonicalized request envelope params; without this the chain commits only to step metadata, not to request content — deployments claiming the **ACS-Audit** profile MUST populate `request_hash`), `timestamp`, `provenance_summary`, `previous_hash` (required for every entry except the first). -Represents a single communication turn or a piece of contextual information between a user and an agent. +Storage representation is implementation-defined; this spec constrains the canonical form used for hashing, not how Guardians store entries internally. +### 8.2 Chain hashing (normative) -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `id` | `string` | Yes | Message Id. A unique identifier must be provided for messages in the same session. | -| `role` | `string` | Yes | Indicates the sender: `user`, `agent` or `system`. | -| `content` | [`Part[]`](#34-part-union-type) | Yes | Array of content parts. Must contain at least one part. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the message. | +`entry_hash = lowercase-hex(SHA-256(content_bytes || prev_hash_bytes))` where: +1. `content_bytes` is the UTF-8 encoding of the RFC 8785 (JCS) canonicalization of the ContextEntry object with `entry_hash` and `previous_hash` fields REMOVED; +2. `prev_hash_bytes` is the raw 32-byte decoding of `previous_hash`, or the empty byte string if `previous_hash` is null/absent (first entry in the session); +3. `||` denotes byte concatenation. -### 3.4. `Part` Union Type +Conformant Guardians MUST compute `entry_hash` this way; otherwise chains computed by different implementations will not match and cross-Guardian audit comparison breaks. Alternative canonicalization schemes are not permitted in v0.1. -Represents a distinct piece of content within a `Message`. A `Part` is a union type representing exportable content as either `TextPart`, `FilePart`, or `DataPart`. All `Part` types also include an optional `metadata` field (`Record`) for part-specific metadata. +### 8.3 ProvenanceSummary +Schema: [`provenance-summary.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/provenance-summary.json). Optional. Condensed view of provenance facts at the entry level (what entered at this step) and at the session level (cumulative across the session). All fields are OPTIONAL — Guardians populate only what their policies consume. Available v0.1 fields: `origins_seen`, `entry_count`, `entry_count_by_origin`, `earliest_step_id_by_origin`, `max_lineage_depth`. v0.1 carries origin-derived aggregates only; trust-derived aggregates are computed Guardian-internally because v0.1 keeps trust classification in policy. The session-level summary is the monotonic aggregation of entry-level summaries. -It **MUST** be one of the following: +### 8.4 Intent -#### 3.4.1. `TextPart` Object +Optional. `raw`, `parsed` (capability list), `parser_provenance` (REQUIRED if `parsed` present; `origin` MUST be `user_input`), `scope_mode`. -For conveying plain textual content. +**Intent immutability (normative).** Once an Intent is established (via `sessionStart` or the first `agentTrigger` for the session), `Intent.parsed` MUST NOT be modifiable by the runtime LLM, by tool outputs, or by any data crossing an `untrusted` channel. The framework enforces immutability; any modification attempt MUST be ignored or rejected, and SHOULD be recorded as an audit event. The only conformant mechanism for extending `Intent.parsed` within a session is an approver's `intent_extension` returned via the ASK flow (§9). This rule is load-bearing for IBAC's central security claim: the capability set is fixed before untrusted data enters and can grow only through explicit, audited approver action. +### 8.5 Size-based archival (optional) -| Field Name | Type | Required | Description | -| :--------- | :-------------------- | :------- | :-------------------------------------------- | -| `kind` | `"text"` (literal) | Yes | Identifies this part as textual content. | -| `text` | `string` | Yes | The textual content of the part. | -| `metadata` | `Record` | No | Optional metadata specific to this text part. | +Guardian Agents MAY archive entries when SessionContext exceeds a configurable byte threshold (suggested 64 KB). Archival MUST preserve `chain_hash`, `provenance_summary`, and `intent`. A mismatched `chain_hash` SHOULD trigger an audit event. -#### 3.4.2. `FilePart` Object +## 9. Escalation / Approver Model -For conveying file-based content. +ASK approvers MAY be human, agent, or service. `ask_details.approver = { type, id, endpoint }`. The Approver receives an ACS-shaped request and returns an ACS-shaped decision. Approver authentication is REQUIRED. Guardian MUST verify approver identity against policy. +Single-hop only in v0.1. Approvers MUST NOT return ASK. Quorum and recursive ASK deferred to v0.2. -| Field Name | Type | Required | Description | -| :--------- | :-------------------- | :---------- | :-------------------------------------------- | -| `kind` | `"file"` (literal) | Yes | Identifies this part as file content. | -| `file` | `FileWithBytes` \| `FileWithUri` | Yes | Contains the file details and data/reference. | -| `metadata` | `Record` | No | Optional metadata specific to this file part. | +### 9.1 Intent extension via ASK (normative) -#### 3.4.3. `DataPart` Object +When a Guardian raises ASK because a request is outside `Intent.parsed`, the approver's grant MAY include an `intent_extension` field (see [`ask-details.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/ask-details.json)) containing capabilities to add to `Intent.parsed`. The extension's `scope` selects between `this_request` (capabilities apply only to the in-flight request) and `session` (capabilities are appended to `Intent.parsed` for the remainder of the session). -For conveying structured JSON data. Useful for forms, parameters, or any machine-readable information. +On `scope: session`, the Guardian MUST: +1. Append the capabilities to `Intent.parsed`. +2. Write a ContextEntry with `step_type: "intent_extension"` recording the approver identity, the granted capabilities, and the originating ASK's `step_id`. +3. Carry the extension's `provenance` forward distinct from the original `parser_provenance` so audits can distinguish parser-derived capabilities from approver-extended ones. -| Field Name | Type | Required | Description | -| :--------- | :-------------------- | :------- | :-------------------------------------------------------------------------- | -| `kind` | `"data"` (literal) | Yes | Identifies this part as structured data. | -| `data` | `Record` | Yes | The structured JSON data payload (an object or an array). | -| `metadata` | `Record` | No | Optional metadata specific to this data part (e.g., reference to a schema). | +Intent extensions are subject to the session's `scope_mode`: a Guardian operating under `scope_mode: strict` MUST NOT honor extensions that would add capabilities the deployment policy forbids in strict mode. This mechanism is the only conformant path to mutate `Intent.parsed` after Intent is committed. -### 3.5.1. `FileWithBytes` Object +### 9.2 Approver-incapable clients (normative) -Represents the data for a file, used within a `FilePart`. +Some Observed Agents — IDE plugins, headless automation, runtimes whose enforcement model is allow/deny only — have no way to route an `ASK` disposition. The Guardian determines client ASK-handling capability by deployment-defined means: agent identity bound at handshake, policy keyed on `agent_id`, organizational configuration, or any other out-of-band signal the deployment trusts. ACS does not put this declaration on the wire in v0.1; it is part of the Guardian's policy bundle. +When the Guardian determines that the client cannot resolve `ASK`, the Guardian MUST NOT return `ASK`. The Guardian MUST instead substitute one of: -| Field Name | Type | Required | Description | -| :--------- | :------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------- | -| `name` | `string` | No | Original filename (e.g., "report.pdf"). | -| `mimeType` | `string` | No | [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) (e.g., `image/png`). Strongly recommended. | -| `bytes` | `string` | Yes | Base64 encoded file content. | +1. `DEFER` with `timeout_decision: "deny"` — when the underlying issue might resolve through retry, an out-of-band escalation, or a later state change. The deferred verdict still counts toward cascading-deferral limits (§6). +2. `DENY` with `reason_codes: ["approver_unavailable"]` and `reasoning` that names the missing capability — when no recovery path exists. -### 3.5.2. `FileWithUri` Object +The choice is policy-driven: deployments SHOULD prefer `DEFER` when the request is potentially recoverable through a different surface, and `DENY` when the action is unconditionally outside the client's reachable authority. -Represents the URI for a file, used within a `FilePart`. +This rule preserves the security guarantee — actions that would have been `ASK`'d in an approver-capable deployment are not silently allowed — while letting clients without approver UX participate in ACS sessions as fully conformant ACS-Core deployments. +## 10. Cryptographic Signatures -| Field Name | Type | Required | Description | -| :--------- | :------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------- | -| `name` | `string` | No | Original filename (e.g., "report.pdf"). | -| `mimeType` | `string` | No | [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) (e.g., `image/png`). Strongly recommended. | -| `uri` | `string` | Yes | URI (absolute URL strongly recommended) to file content. Accessibility is context-dependent. | +Optional at the field level; when signatures are negotiated, the envelope is `{ algorithm, value, key_id }` with the algorithm chosen from the registry below. Supported algorithms are declared per-direction in the handshake. +### 10.1 Algorithm registry -### 3.6. `AgentTrigger` Object +The registry is crypto-agile: the `{ algorithm, value, key_id }` envelope supports any registered algorithm, and the handshake declares which algorithms each side supports. v0.1 prioritizes adoption breadth over cryptographic maximalism — signatures are already field-optional, and a spec that ships without PQC mandates protects more deployments than a spec that mandates PQC and doesn't ship. PQC algorithms from NIST FIPS 203–205 (2024) are registered and available; a future version is expected to promote PQC to RECOMMENDED once ecosystem support matures. -Represents the trigger that resulted in agent activation.It can be a recurring trigger or responding to an event.
-User prompt is not included and it is included in `Message`. +| Algorithm | Class | v0.1.0 status | +|---|---|---| +| `HMAC-SHA256` | Symmetric MAC | RECOMMENDED — shared-secret integrity; simplest deployment path. Sufficient for same-host and trusted-network topologies where the threat is accidental tampering or replay. | +| `ECDSA-P256` | Classical asymmetric | OPTIONAL — strongest current ecosystem support across Java, Node, .NET, HSMs, and major cloud KMS providers. | +| `RSA-PSS-SHA256` | Classical asymmetric | OPTIONAL — legacy interop; deployments with existing RSA PKI. | +| `ML-DSA-65` | PQC, lattice (FIPS 204) | OPTIONAL — ~128-bit post-quantum security; ~3.3 KB signatures. Recommended for deployments shipping PQC libraries today. | +| `ML-DSA-44` | PQC, lattice | OPTIONAL — low-bandwidth profile. | +| `ML-DSA-87` | PQC, lattice | OPTIONAL — high-security profile. | +| `SLH-DSA-128s` | PQC, hash (FIPS 205) | OPTIONAL — algorithmic diversity vs. ML-DSA's lattice assumption. Caution: ~7.8 KB signatures, signing takes hundreds of milliseconds — unsuitable for hot-path Guardian responses without careful latency budgeting. | +| `SLH-DSA-128f` | PQC, hash | OPTIONAL — faster signing; larger signatures. | +| `ML-DSA-65+ECDSA-P256` | Hybrid | OPTIONAL — transitional composite for PQC forward-resistance with classical co-signature. | +| `ML-DSA-65+RSA-PSS-SHA256` | Hybrid | OPTIONAL — transitional composite. | +**PQC migration intent.** The long-term direction is PQC-primary. A future ACS version is expected to promote `ML-DSA-65` to RECOMMENDED and eventually deprecate classical-only algorithms, but the timeline depends on ecosystem readiness — library maturity across Java/Node/.NET, HSM/KMS support breadth, and operational experience at scale. The crypto-agile envelope ensures that migration requires no wire-format changes; only the handshake-negotiated algorithm set shifts. -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `type` | `string` | Yes | Type of the trigger. Allowed values: `autonomous`. | -| `event` | [`AgentTriggerEvent`](#361-agenttriggerevent-object) | Yes | The triggering event. | -| `content` | [`Part[]`](#34-part-union-type) | Yes | Array of content parts. Must contain at least one part. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the trigger. | +### 10.2 Hybrid signature value encoding -#### 3.6.1. `AgentTriggerEvent` Object +For any algorithm of the form `+`, the `value` field carries the concatenation `len(pqc_sig) || pqc_sig || len(classical_sig) || classical_sig`, where each `len` is a 4-byte big-endian unsigned integer and the whole blob is base64-encoded for wire transit. Verifiers MUST verify both component signatures over the same canonical input; failure of either component is a signature failure. The same `key_id` resolves to a hybrid key descriptor that pins both component public keys. +### 10.3 Replay protection -Info about triggering event. +`request_id` (UUID), `timestamp` (ISO 8601), and optional `nonce` (16–64 bytes). Guardians MUST reject requests whose `timestamp` is more than the handshake-negotiated skew window in the past or future, MUST reject duplicate `request_id` values within the session, and SHOULD reject duplicate `nonce` values within a sliding window the deployment configures. +## 11. Platform / OS Independence -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `type` | `string` | Yes | Type of the event. Examples: email, recurring. | -| `id` | `string` | Yes | The Id of the triggering event. | +ACS MUST be deployable across IDE, SaaS, on-prem on Linux/Windows/macOS/mobile/browser. Normative: +- Resource identifiers MUST use URI form (`file:///C:/...`, `posix:///etc/...`, `https://...`). +- Capability vocabulary uses abstract names (`filesystem.delete`, `network.egress`, `process.execute`). +- Identity descriptors carry a `type` discriminator (`posix_uid`, `windows_sid`, `oauth_subject`, `cert_subject`, …). Schema owned by the Identity workstream. +- Authentication mechanism declared in handshake; spec mandates none. -### 3.7. `Source` Union Type +## 12. Policy Engine and Agent Layers -Represents a source that is used as a reference or citation in the Agent response (`Message`) to justify or explain the output.
-A `Source` is a union type representing the cited source as either `FileSource` or `SiteSource`. +### 12.1 Policy engine interface +Spec defines the **interface** to the deterministic-layer engine, not the engine. -It **MUST** be one of the following: +**Input:** request envelope + SessionContext + Intent + provenance. **Output:** decision envelope, plus optional `delegate_to: "agent"`. -#### 3.7.1. `FileSource` Object -For conveying file source. +| Engine | Status | +|---|---| +| OPA / Rego | v0.1 starting reference | +| Cedar | v0.2 fast-follow | +Custom engines plug in by respecting the interface. ACS layers conventions on top: modification format, reasoning format, no external HTTP calls in canonical policies, bundle layout, baseline policy bundle. -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `kind` | `"file"` (literal) | Yes | Identifies this source as file source. | -| `id` | `string` | Yes | The Id of the file. The Id is identical to the `Resource` id in the list of agent's available resources (`resources`) if found. | -| `name` | `string` | Yes | The name of the file (e.g., report.pdf). | -| `url` | `string` | No | The url of the file if the file is available remotely to the agent. For example url to Sharepoint or GoogleDrive. | +### 12.2 Agent layer +Invoked by the deterministic layer's chain config. Receives the same input plus the deterministic layer's intermediate output. Returns a decision envelope. -#### 3.7.2. `SiteSource` Object -For conveying site source. +- Prompt MUST treat untrusted data as data, not instructions. Untrusted fields MUST be wrapped/quoted. +- MUST NOT have access to deterministic-layer policy code. +- Decisions MUST be logged with reasoning, model identifier, confidence (when available). +- Timeouts follow the handshake's `timeout_config`. +OPTIONAL for v0.1.0. Deterministic-only deployments are fully conformant. -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `kind` | `"site"` (literal) | Yes | Identifies this source as file source. | -| `url` | `string` | Yes | The url of the referenced site. | +## 13. Liveness / System Methods -### 3.8. `StepContext` Object +A liveness method is required for connection-health checks, transport-debugging, and timeout tuning. It carries no enforcement semantics and is not part of the audit chain. -Holds information about the context of agent step +**Method:** `system/ping`. Schema: [`hooks/system-ping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/system-ping.json). -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `agent` | [`Agent`](#31-agent-object) | Yes | Holds information about the agent. | -| `session` | [`Session`](#39-session-object) | Yes | Holds information about the current conversation / interaction with the agent. | -| `turnId` | `string` | Yes | A unique turn Id in the current session. | -| `stepId` | `string` | Yes | A unique step Id in the current turn. | -| `timestamp` | `string` (ISO 8601) | Yes | Timestamp (UTC recommended) when this step was recorded. | -| `identity` | [`Identity`](#317-identity-object) | Yes | The identity interacting or triggering the agent. | +**Request payload.** Standard ACS envelope with `method: "system/ping"` and `payload: { "echo": "" }`. +**Response.** Standard ACS decision envelope with `decision: "allow"` and a `payload` object carrying `{ "status": "ok", "echo": "", "server_timestamp": "" }`. -### 3.9. `Session` Object +**Normative rules:** -Holds information about the session. +- Guardians MUST always return `decision: "allow"` for `system/ping` regardless of policy, signature, or session state. The method does not represent a controllable agent action. +- `system/ping` MUST NOT be written into SessionContext as a ContextEntry; it does not participate in the chain hash. +- `system/ping` MUST NOT require a signature even if the session otherwise requires signatures, so that liveness probing remains possible during signature-rotation or key-resolution failures. +- Connection failure or response timeout for `system/ping` is a transport-level signal that the Observed Agent MAY use to renegotiate transport, re-handshake, or fail over; it MUST NOT be interpreted as an enforcement event. +- `system/ping` is the only method in the `system/*` namespace defined in v0.1.0. The namespace is reserved for future low-level transport/control methods (e.g. `system/handshake_renegotiate` in v0.2). +## 14. Multi-Tenancy +`tenant_id` reserved as an optional envelope field. No isolation rules in v0.1. Per-tenant policy scoping, SessionContext isolation, audit boundaries, and cross-tenant A2A rules deferred to v0.2. -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `id` | `string` | Yes | A unique identifier of the session. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the session. | +## 15. Out of Scope (Deferred) -### 3.10. `User` Object +| Feature | Deferred to | Reason | +|---|---|---| +| Streaming + wrapped streaming methods | v0.2 | SSE, interruption, chunk-level policy is too much surface | +| Batching atomicity, ordering, cross-request dependencies | v0.2 | Standard JSON-RPC 2.0 batching is permitted in v0.1; ACS-specific atomicity / ordering / dependency rules wait for the streaming spec | +| Sensitivity / four-level timeout model | v0.2 | Categorization-from-facts is non-trivial; v0.1 uses a single handshake-negotiated default timeout | +| Recursive ASK + quorum | v0.2 | Bounded delegation and tie-breaking need careful spec | +| Multi-tenant isolation rules | v0.2 | Touches policy, SessionContext, audit, A2A | +| `protocols/A2A/*` wrapping specification | v0.2 | A2A hook wrapping is reserved (namespace exists); detailed method mapping waits | +| AgBOM federation across A2A peers | v0.2 | Single-agent AgBOM is in v0.1; federated views need an A2A-side discovery method first | +| gRPC, unix_socket transports | v0.2+ | HTTP + stdio cover IDE/SaaS/on-prem in v0.1 | +| PQC as RECOMMENDED default | Future | Promote `ML-DSA-65` once ecosystem readiness justifies it | +| Classical-only signature deprecation | Future | Deprecate classical algorithms once PQC-only is universal | -Holds information about the user involved in the interaction with the agent. Used when the agent is triggered by a user prompt. +## 16. Roadmap +| Version | Theme | Highlights | +|---|---|---| +| **v0.1.0** | Baseline + profiles | ACS-Core (mandatory), ACS-Trace, ACS-Inspect/Inspect-Dynamic, ACS-Provenance, ACS-Crypto, ACS-Audit profiles | +| **v0.2.0** | Async + composition | Streaming, wrapped streaming, batching atomicity / ordering / dependency semantics, recursive ASK, quorum, multi-tenant isolation, Cedar binding, connection reuse, sensitivity-tier timeout model, AgBOM federation across A2A peers | +| **v0.3.0+** | Reach + transport | gRPC and unix_socket transports, A2A/MCP `deny`/`modify` extensions, classical-only signature deprecation, full deployment-mode taxonomy | -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `id` | `string` | Yes | User Id. | -| `name` | `string` | No | User name. | -| `email` | `string` | No | User email. | -| `organization` | [`Organization`](#311-organization-object) | Yes | The organization that the user belong to. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the user. | +## 17. Error Handling -### 3.11. `Organization` Object +ACS uses standard [JSON-RPC 2.0 error codes](https://www.jsonrpc.org/specification#error_object). The `-32000` to `-32099` range is reserved for ACS-specific errors. -Represents an organization. Used in: `Agent` as the owning organization of the agent, `User` as the organization the user belongs to. - - -| Field Name | Type | Required | Description | -| :---------------------------------- | :----------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `id` | `string` | Yes | Organization Id. | -| `name` | `string` | No | Human-readable name of the organization. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the organization. | - -### 3.12. JSON-RPC Structures - -ACS adheres to the standard [JSON-RPC 2.0](https://www.jsonrpc.org/specification) structures for requests and responses. - -#### 3.12.1. `JSONRPCRequest` Object - -All ACS method calls are encapsulated in a JSON-RPC Request object. - -- `jsonrpc`: A String specifying the version of the JSON-RPC protocol. **MUST** be exactly `"2.0"`. -- `method`: A String containing the name of the method to be invoked (e.g., `"steps/knowledge"`, `"messages/mcp"`). -- `params`: A Structured value that holds the parameter values to be used during the invocation of the method. This member **MAY** be omitted if the method expects no parameters. ACS methods typically use an `object` for `params`. -- `id`: An identifier established by the Client that **MUST** contain a String or Number(Integer) value. The Guardian Agent **MUST** reply with the same value in the Response object. This member is used to correlate the context between the two request and response objects. - -#### 3.12.2. `JSONRPCResponse` Object - -Responses from the Guardian Agent are encapsulated in a JSON-RPC Response object. - -- `jsonrpc`: A String specifying the version of the JSON-RPC protocol. **MUST** be exactly `"2.0"`. -- `id`: This member is **REQUIRED**. It **MUST** be the same as the value of the `id` member in the Request Object. If there was an error in detecting the `id` in the Request object (e.g. Parse error/Invalid Request), it **MUST** be `null`. -- **EITHER** `result`: This member is **REQUIRED** on success. This member **MUST NOT** exist if there was an error invoking the method. The value of this member is determined by the method invoked on the Guardian Agent. -- **OR** `error`: This member is **REQUIRED** on failure. This member **MUST NOT** exist if there was no error triggered during invocation. The value of this member **MUST** be an [`JSONRPCError`](#313-jsonrpcerror-object) object. -- The members `result` and `error` are mutually exclusive: one **MUST** be present, and the other **MUST NOT**. - -### 3.13. `JSONRPCError` Object - -When a JSON-RPC call encounters an error, the Response Object will contain an `error` member with a value of this structure. - - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `code` | `integer` | Yes | Integer error code. See [Section 6 (Error Handling)](#6-error-handling) for error codes. | -| `message` | `string` | Yes | Short, human-readable summary of the error. | -| `data` | `any` | No | Optional additional structured information about the error. | - - -### 3.14. `KnowledgeRetrievalStepParams` Object -Holds the parameters for the knowledge retrieval step. See `steps/knowledgeRetrieval` for more info. - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `query` | `string` | No | The query extracted from agent's input and used to fetch data / knowledge. | -| `keywords` | `string[]` | No | Keywords used to fetch data / knowledge. Usually used with word matching search. | -| `results` | [`KnowledgeRetrievalResult`](#3141-knowledgeretrievalresult-object)[] | Yes | Array of retrieved knowledge. | - - -#### 3.14.1. `KnowledgeRetrievalResult` Object -Represents a result of knowledge retrieval process. - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `id` | `string` | Yes | The Id of the source. The Id is identical to the `Resource` id in the list of agent's available resources (`resources`) if found. | -| `content` | `string` | Yes | The retrieved content from the source. | -| `mimeType` | `string` | No | [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) (e.g., text/plain, image/png). Strongly recommended. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the retrieved result. | - - -### 3.15. `ToolCallRequest` Object -Information about the tool call request.
-Agents use tools to complete tasks and fulfill user's requests. Available tools might be listed in [`Agent`](#31-agent-object) object under `tools`.
-Using descriptions from [`ToolDefinition`](#32-tooldefinition-object), the agent decide on which tool to call and on arguments to provide.
- -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `executionId` | `string` | Yes | Execution id of the tool provided by the orchestrator/planner. This id is used later on by the agent and LLM to correlate between tool call request and result. | -| `toolId` | `string` | Yes | The Id of the tool as specified in [`ToolDefinition`](#32-tooldefinition-object) if exists in `Agent`'s tools. | -| `inputs` | [`ToolArgumentValue`](#3151-toolargumentvalue-object)[] | Yes | Array of inputs for the tool. | - - -#### 3.15.1. `ToolArgumentValue` Object -Defines a single argument / input for the tool. - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `name` | `string` | Yes | The name of the argument. This is correlated with argument's name as specified in [`ToolArgumentDefinition`](#321-toolargumentdefinition-object) | -| `id` | `string` | No | The id of the argument. It is correlated with argument's id as specified in [`ToolArgumentDefinition`](#321-toolargumentdefinition-object) | -| `value` | `string`\| `number` \| `boolean` \| `object` \| `array` \| `null` | Yes | The argument's value. | - - -### 3.16. `A2AContext` Object -Context for A2A requests and responses - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `from` | [`A2AFullAgentContext`](#3161-a2adullagentcontext-object) \| [`A2APartialAgentContext`](#3162-a2apartialagentcontext-object) | Yes | Details of the agent that is sending the A2A message (request or response). `A2AFullAgentContext` when observed agent is the client, `A2APartialAgentContext` when observed agent is the server. | -| `to` | [`A2AFullAgentContext`](#3161-a2afullagentcontext-object) \| [`A2APartialAgentContext`](#3162-a2apartialagentcontext-object) | Yes | Details of the agent that is receiving the A2A message (request or response). `A2AFullAgentContext` when observed agent is the server, `A2APartialAgentContext` when observed agent is the client. | - - -#### 3.16.1. `A2AFullAgentContext` Object -Object representing the sender agent in the A2A protocol communication - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `agent` | [`Agent`](#31-agent-object) | Yes | Details of the agent that is sending the A2A message (either request or response). | -| `role` | `"client"` \| `"server"` (literal) | Yes | Role of the agent as defined in A2A protocol terminology. `"client"` for agent that initializes tasks and requests, `"server"` for agent that fulfills tasks and requests. - - -#### 3.16.2. `A2APartialAgentContext` Object -Object representing the receiver agent in the A2A protocol communication - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `agent` | [`A2APartialAgentDetails`](#3163-a2apartialagentdetails-object) | Yes | Details of the agent that is receiving the A2A message (either request or response). | -| `role` | `"client"` \| `"server"` (literal) | Yes | Role of the agent as defined in A2A protocol terminology. `"client"` for agent that initializes tasks and requests, `"server"` for agent that fulfills tasks and requests. - - -#### 3.16.3. `A2APartialAgentDetails` Object -Object representing the receiver agent in the A2A protocol communication - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `url` | `string` | Yes | A URL to the address the receiving agent is hosted at as it appears in its [AgentCard](https://google-a2a.github.io/A2A/latest/specification/#55-agentcard-object-structure). | -| `name` | `string` | Yes | The name of the receiving agent as it appears in its AgentCard. -| `version` | `string` | Yes | The version of the receiving agent as it appeard in its AgentCard. -| `identity` | [`AgentIdentity`](#318-agentidentity-object) | Yes | Identity of the agent. - - -### 3.17. `Identity` Object -Object representing identity in the context. This is a union object that can be one of the following: -- [`User`](#310-user-object) -- [`MachineIdentity`]() -- [`AgentIdentity`](#318-agentidentity-object) - - -#### 3.17.1 `MachineIdentity` Object -Object representing a machine identity - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `id` | `striung` | Yes | The unique identifier of the application or service. | -| `name` | `string`| Yes | The name of the application or service. | -| `organization` | [`Organization`](#311-organization-object)[] | Yes | The owning organization of the application or service. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the identity. | - - -### 3.18. `AgentIdentity` Object -Object representing an agent identity - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `signatures` | [`AgentSignature`](#3181-agentsignature-object)[] | Yes | Array of JSON Web Signatures computed for the agent. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the identity. | - - - -#### 3.18.1. `AgentSignature` Object -Object representing an agent signature as an agent identifier - -| Field Name | Type | Required | Description | -| :--------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------- | -| `header` | `string` | Yes | The unprotected JWS header values. | -| `protected` | `string` | Yes | The protected JWS header for the signature. This is a Base64url-encoded\nJSON object, as per RFC 7515. | -| `signature` | `string` | Yes | The computed signature, Base64url-encoded. | - - -## 4. Protocol RPC Methods -All ACS RPC methods are invoked by the agent by sending an HTTP POST request to the guardian agent. The body of the HTTP POST request **MUST** be a `JSONRPCRequest` object, and the `Content-Type` header **MUST** be `application/json`. - -The guardian's agent HTTP response body **MUST** be a `JSONRPCResponse` object. The `Content-Type` for JSON-RPC responses is `application/json`.
- -Most of the protocol methods refer to steps within the agent's workflow: tool call, knowledge, memory etc.
-ACS also supports industial standards for Agent to Agent communication (A2A protocol) and tool call and context (MCP protocol). - -### 4.1. steps/agentTrigger -This is the first step that activates or triggers the agent as a result or a response to an event.
-This method should be used after the agent's input is extracted from the trigger and before it gets to the agent. - - -#### 4.1.1. **Request `params` Object** - - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `context` | [`StepContext`](#38-stepcontext-object) | Yes | The context of the current step. | -| `trigger` | [`AgentTrigger`](#36-agenttrigger-object) | Yes | The trigger that activated the agent. | - - -#### 4.1.2. **Response on success**: [`ACSSuccessResponse`](#51-ACSSuccessResponse-object). -#### 4.1.3. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - - -### 4.2. steps/knowledgeRetrieval -This step refers to the process of fetching relevant information from an external source (like document store, vector databse, API etc.) to ground agent's response in facts, context and data.
-Result can be a chunk or multiple chunks of data from a source.
-There are many retrieval techniques including semantic search (embedding-based similarity) and keyword search (exact/partial matching), or any combination. - - -#### 4.2.1. **Request `params` Object** - - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `context` | [`StepContext`](#38-stepcontext-object) | Yes | The context of the current step. | -| `knowledgeStep` | [`KnowledgeRetrievalStepParams`](#314-knowledgeretrievalstepparams-object) | Yes | Knowledge retrieval step parameters. | -| `reasoning` | `string` | No | Agent's reasoning. | - - -#### 4.2.2. **Response on success**: [`ACSSuccessResponse`](#51-ACSsuccessresponse-object). -#### 4.2.3. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - -### 4.3. steps/memoryStore -This step refers to the process of memorizing and store memory to the memory store for additional context for future or current agent interactions.
-Mostly, interaction history or a summary is stored to the memory store. - -#### 4.3.1. **Request `params` Object** - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `context` | [`StepContext`](#38-stepcontext-object) | Yes | The context of the current step. | -| `memory` | `string`[]| Yes | Array of retrieved memory contents. For JSON structured memory (for example chat history), a stringified JSON should be provided. | -| `reasoning` | `string` | No | Agent's reasoning. | - - -#### 4.3.2. **Response on success**: [`ACSSuccessResponse`](#51-ACSsuccessresponse-object). -#### 4.3.3. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - - -### 4.4. steps/memoryContextRetrieval -This step refers to the process of retrieving memory to add to the context of the current agent interactions.
-This context is passed alongside with the agent's instructions(system prompt), user prompt and additional information such as retrieved knowledge to the LLM. - -#### 4.4.1. **Request `params` Object** - - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `context` | [`StepContext`](#38-stepcontext-object) | Yes | The context of the current step. | -| `memory` | `string`[]| Yes | Array of retrieved memory contents. For JSON structured memory (for example chat history), a stringified JSON should be provided. | -| `reasoning` | `string` | No | Agent's reasoning. | - - -#### 4.4.2. **Response on success**: [`ACSSuccessResponse`](#51-ACSsuccessresponse-object). -#### 4.4.3. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - - -### 4.5. steps/message -Message step refers to the agent's input or output - depends on the `role`.
-A message with `user` role represents the user prompt / agent's input from the user. The method with `user` message **must** be used before the extracted input gets into the agent.
-A message with `agent` role represents the agent's output (AI response).The method with `agent` message **must** be used after the agent's response is ready and before it is sent back to the user.
-A message with `system` role represents a message from the system, such as guardrails controls etc. The method with `system` message **must** be used a before it is sent back to the user. - -#### 4.5.1. **Request `params` Object** - - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `context` | [`StepContext`](#38-stepcontext-object) | Yes | The context of the current step. | -| `message` |[`Message`](#33-message-object)| Yes | The message. | -| `citation` | [`Source`](#37-source-union-type)[]| Yes | Array of referenced sources. Relevant mostly with `agent` message. | -| `reasoning` | `string` | No | Agent's reasoning. Should be used with `agent` or `system` message. | - - -#### 4.5.2. **Response on success**: [`ACSSuccessResponse`](#51-ACSsuccessresponse-object). -#### 4.5.3. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - - -### 4.6. steps/toolCallRequest -Agents use tools to complete tasks and fulfill user's requests.
-This method should be used after tool inputs are inferred by the LLM and before calling the tool. - -#### 4.6.1. **Request `params` Object** - - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `context` | [`StepContext`](#38-stepcontext-object) | Yes | The context of the current step. | -| `toolCallRequest` |[`ToolCallRequest`](#315-toolcallrequest-object)| Yes | Tool call request details. | -| `reasoning` | `string` | No | Agent's reasoning. | - - - -#### 4.5.2. **Response on success**: [`ACSSuccessResponse`](#51-ACSsuccessresponse-object). -#### 4.5.3. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - -### 4.6. steps/toolCallResult -This method should be used after tool is completed and before the result goes back into the LLM for further processing. - -#### 4.6.1. **Request `params` Object** - - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `context` | [`StepContext`](#38-stepcontext-object) | Yes | The context of the current step. | -| `executionId` |`string`| Yes | Execution id, correlated with executionId in [`ToolCallRequest`](#315-toolcallrequest-object) that was previously provided in `steps/toolCallRequest`. | -| `result` |[`ToolCallResult`](#4611-toolcallresult-object)| Yes | Result. | - - -##### 4.6.1.1. `ToolCallResult` Object -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `outputs` | [`TextPart`](#341-textpart-object)[] | Yes | Array of `TextPart`. Can be empty if tool does not have output. | -| `isError` |`boolean`| Yes | Whether tool completed successfully or resulted in an error. | - - -#### 4.6.2. **Response on success**: [`ACSSuccessResponse`](#51-ACSsuccessresponse-object). -#### 4.6.3. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - -### 4.7. ping -This method is used by the agent to ensure that guardian agent is alive. - - -#### 4.7.1. **Request `params` Object** - - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `timestamp` | `string` (ISO 8601) | Yes | Timestamp (UTC recommended). | -| `timeout` | `integer`| No | Timeout in milliseconds after which the communication with guardian agent is considered to be lost. | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the agent. | - -#### 4.7.2. **Response on success**: [`PingRequestSuccessResponse`](#53-pingrequestsuccessresponse-object). -#### 4.7.3. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - -### 4.8. A2A Requests -Every [A2A](https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/) protocol method (request) has its corresponding method in ACS. The structure of these methods are similar and comply with JRPC request structure.
-For client agent, these methods should be used before sending A2A request to a server (remote) agent to monitor outbound communications.
-For server agent, these methods should be used before processing A2A request from client agent to monitor inbound communications.
-Read more about A2A support in [extend_a2a](../instrument/a2a/extend_a2a.md). - - -#### 4.8.1. A2A `Request` Object structure - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `id` | `string` \| `integer` | Yes | Unique id of the request. | -| `jsonrpc` |`"2.0"` (literal)| Yes | JSON-RPC version string. | -| `method` | `string` | Yes | Method name. Same method name as found in `method` field in the A2A message. See [A2A supported methods](#482-a2a-supported-methods) for the full list. | -| `payload` | `object` | Yes | A2A raw JSON message. | -| `context` | [`A2AContext`](#316-a2acontext-object) | Yes | The context of the current A2A message. | -| `reasoning` | `string` | No | Agent's reasoning. | - -#### 4.8.2. A2A supported methods -- `message/send` -- `message/stream` -- `tasks/pushNotificationConfig/set` -- `tasks/pushNotificationConfig/get` -- `tasks/resubscribe` -- `tasks/cancel` -- `tasks/get` - - -#### 4.8.3. **Response on success**: [`ACSSuccessResponse`](#51-ACSsuccessresponse-object). -#### 4.8.4. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - -### 4.9. A2A Responses -Every response of [A2A supported methods](#482-a2a-supported-methods) from server agent has its corresponding ACS request.
- -For client agents, these methods should be used before the response from the server agent is processed by the client observed agent to monitor inbound communications.
-For server agents, these methods should be used before the response the response is sent back to the client agent to monitor outbound communications.
-Read more about A2A support in [extend_a2a](../instrument/a2a/extend_a2a.md). - -#### 4.9.1. A2A `Request` Object structure - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `id` | `string` \| `integer` | Yes | Unique id of the request. | -| `jsonrpc` |`"2.0"` (literal)| Yes | JSON-RPC version string. | -| `method` | `string` | Yes | Method name. Same method name as found in `method` field in the A2A original corresponding request message. See [A2A supported methods](#482-a2a-supported-methods) for the full list. | -| `context` | [`A2AContext`](#316-a2acontext-object) | Yes | The context of the current A2A message. | -| `payload` | `object` | Yes | A2A raw JSON message (response). | - - -#### 4.9.2. **Response on success**: [`ACSSuccessResponse`](#51-ACSsuccessresponse-object). -#### 4.9.3. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - - -### 4.10. protocols/MCP -This method should be used to wrap all [MCP](https://modelcontextprotocol.io/introduction) communications and messages.
-This method should be used before sending MCP message to MCP server to monitor outbound communications.
-This method should be used after receiving MCP message (response) from a MCP server to monitor inbound communications.
-Read more about MCP support in [extend_mcp](../instrument/extend_mcp.md). - - -#### 4.10.1. **Request `params` Object** - - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `message` | `object` | Yes | MCP-compliant message. | -| `reasoning` | `string` | No | Agent's reasoning. | - -#### 4.10.2. **Response on success**: [`ACSSuccessResponse`](#51-ACSsuccessresponse-object). -#### 4.10.3. **Response on failure**: [`JSONRPCErrorResponse`](#313-jsonrpcerrorresonse-object). - - -## 5. Responses - -### 5.1. `ACSSuccessResponse` Object -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `id` | `string` \| `integer` | Yes | Same id as the id in the correlated request. | -| `jsonrpc` |`"2.0"` (literal)| Yes | JSON-RPC version string. | -| `result` |[`ACSSuccessResult`](#511-ACSsuccessresult-object)| Yes | Success result. | - -#### 5.1.1. `ACSSuccessResult` Object - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `decision` | `string` | Yes | Guardian agent's de. One of `allow`, `deny`, `modify`. | -| `reasoning` | `string`| No | Guardian agent's reasoning/thought explaining the decision. | -| `reasonCode` | `string`[] | No | Timestamp (UTC recommended). | -| `message` | `string`| Yes | Human readable message explaining the decision. | -| `data` | `Record` | No | Additional key-value data. | -| `modifiedRequest` | `ACSRequest` | No | Modified request. This is relevant when decision is `modify`. | - -### 5.2.`JSONRPCErrorResponse` Object -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `id` | `string` | Yes | Response id. This should be the same as the request id. | -| `error` | `string`| Yes | One of `JSONRPCError`, `JSONParseError`, `InvalidRequestError`, `MethodNotFoundError`, `InvalidParamsError`, `InternalError`. | -| `jsonrpc` |`"2.0"` (literal)| Yes | JSON-RPC version string. | - - - -### 5.3. `PingRequestSuccessResponse` Object -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `id` | `string` \| `integer` | Yes | Same id as the id in the correlated ping request. | -| `result` |[`PingRequestResult`](#531-pingrequestresult-object)| Yes | Ping result. | -| `jsonrpc` |`"2.0"` (literal)| Yes | JSON-RPC version string. | - - -#### 5.3.1. `PingRequestResult` Object - -| Field Name | Type | Required | Description | -| :-------------- | :-------------------------------------------------------------- | :------- | :----------------------------------------------------------------- | -| `status` | `string` | Yes | Guardian agent's status. One of `connected`, `error`. | -| `version` | `string`| Yes | Guardian agent's version. | -| `timestamp` | `string` (ISO 8601) | Yes | Timestamp (UTC recommended). | -| `metadata` | `Record` | No | Arbitrary key-value metadata associated with the agent. | - -## 6. Error Handling - -ACS uses standard [JSON-RPC 2.0 error codes and structure](https://www.jsonrpc.org/specification#error_object) for reporting errors. Errors are returned in the `error` member of the `JSONRPCErrorResponse` object. See [`JSONRPCError` Object definition](#313-jsonrpcerror-object). - -### 6.1. Standard JSON-RPC Errors - -These are standard codes defined by the JSON-RPC 2.0 specification. - -| Code | JSON-RPC Spec Meaning | Typical ACS `message` | Description | -| :------------------- | :-------------------- | :------------------------ | :------------------------------------------------------------------------------------------- | -| `-32700` | Parse error (JSONParseError) | Invalid JSON payload | Server received JSON that was not well-formed. | -| `-32600` | Invalid Request (InvalidRequestError) | Invalid JSON-RPC Request | The JSON payload was valid JSON, but not a valid JSON-RPC Request object. | -| `-32601` | Method not found (MethodNotFoundError) | Method not found | The requested ACS RPC `method` (e.g., `"steps/foo"`) does not exist or is not supported. | -| `-32602` | Invalid params (InvalidParamsError) | Invalid method parameters | The `params` provided for the method are invalid (e.g., wrong type, missing required field). | -| `-32603` | Internal error (InternalError) | Internal server error | An unexpected error occurred on the server during processing. | -| `-32000` to `-32099` | Server error | _(Server-defined)_ | Reserved for implementation-defined server-errors. ACS-specific errors use this range. | +| Code | Meaning | Typical use | +|---|---|---| +| `-32700` | Parse error | Invalid JSON payload | +| `-32600` | Invalid Request | Not a valid JSON-RPC Request, or array-shaped input to a Guardian that does not support batching | +| `-32601` | Method not found | The requested ACS method does not exist or is not supported | +| `-32602` | Invalid params | `params` are invalid (wrong type, missing required field) | +| `-32603` | Internal error | Unexpected server error | +| `-32000` to `-32099` | Server error | Reserved for ACS-specific errors (e.g. `UNSUPPORTED_VERSION`, `SESSION_REFUSED`, `REPLAY_DETECTED`) | diff --git a/docs/spec/trace/events.md b/docs/spec/trace/events.md index 016a31f..c1f49cc 100644 --- a/docs/spec/trace/events.md +++ b/docs/spec/trace/events.md @@ -1,367 +1,70 @@ -# ACS Supported Events +# Trace Events -Every agent action becomes observable. Every decision gets traced. Every communication leaves a trail. +Every agent step that produces an ACS hook is recordable as a Trace event. The Trace pillar is decoupled from enforcement and runs out of the request/response path: deployments emit OpenTelemetry spans (over OTLP/gRPC or OTLP/HTTP) and/or OCSF events to existing observability backends. ACS does not redefine a trace transport. Its normative contribution is the **vocabulary** — span names, attribute keys, event classes, severity/disposition mapping — that ensures cross-vendor agents and Guardians produce comparable traces. -Agent Control Standard (ACS) transforms opaque AI systems into transparent, auditable processes through standardized event emission. This document defines the canonical events that enable trust through visibility. +Trace emission is the subject of the **ACS-Trace** [conformance profile](../conformance.md#acs-trace). Deployments that implement only ACS-Core (Instrument) without Trace are v0.1.0-conformant but do not claim ACS-Trace. -## Event Classification -All observable actions in ACS are considered **Agent Steps**. Each step represents a single, traceable action taken by the agent or the system. To enhance explainability and provide a clear structure for analysis, these steps are classified into the following groups based on their purpose. This classification is for semantic grouping and does not imply a technical difference in the underlying event structure. +## OpenTelemetry semantic conventions -| Category | Description | Events | -|----------|-------------|---------| -| Execution Steps | Core execution flow events that track what agents do | • Message processing (user inputs, agent outputs)
• Tool execution (requests and results)
• Memory operations (storage and retrieval)
• Knowledge queries (RAG and search)
• Agent activation (triggers and initialization) | -| Decision Events | Guardian agent decisions on every action | • **Allow**: Action proceeds unchanged
• **Deny**: Action blocked with explanation
• **Modify**: Action altered with new parameters | -| Protocol Events | Inter-system communication traces | • **A2A Protocol**: Agent-to-agent messages
• **MCP Protocol**: Model Context Protocol interactions | -| Agent Composition (Bill of Materials) | Dynamic updates to the agent's components and capabilities. | • Agent capabilities changed
• MCP server connection changed
• Knowledge source changed
• Tool changed
• Memory configuration changed
• Model changed | -| System Events | Operational health and diagnostics | • Health checks and heartbeats
• Error conditions and failures
• Performance metrics | +Each ACS step produces a span whose `name` and required attributes are fixed by the table below. Decisions are recorded as span events on the parent step span, not as separate spans, so the enforcement verdict and the action it gates share a parent. -## Event Reference +The full mapping lives in [`trace/otel-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/trace/otel-mapping.json). See [Extending OpenTelemetry](./extend_opentelemetry.md) for a deeper integration guide. -### `steps/message` +| ACS step | OTel span name | Required attributes | +|---|---|---| +| `steps/sessionStart` | `acs.session` | `acs.session.id`; `acs.tenant_id` if set | +| `steps/agentTrigger` | `acs.agent.trigger` | `acs.agent.id`, `acs.trigger.type` | +| `steps/userMessage` | `acs.message.user` | `acs.session.id`, `acs.content.types[]` | +| `steps/agentResponse` | `acs.message.agent` | `acs.session.id`, `acs.agent.id` | +| `steps/toolCallRequest` | `gen_ai.tool.call` | `gen_ai.tool.name`, `acs.capability`, `acs.tool.provider` | +| `steps/toolCallResult` | `gen_ai.tool.result` | `gen_ai.tool.name`, `acs.exit_status`, `acs.duration_ms` | +| `steps/knowledgeRetrieval` | `acs.knowledge.retrieval` | `acs.source.type`, `acs.results.count` | +| `steps/memoryStore` | `acs.memory.store` | `acs.memory_store.name`, `acs.operation` | +| `steps/memoryContextRetrieval` | `acs.memory.retrieval` | `acs.memory_store.name`, `acs.results.count` | +| `steps/sessionEnd` | `acs.session.end` | `acs.session.reason` | +| `steps/turnStart`, `steps/turnEnd` | `acs.turn`, `acs.turn.end` | `acs.turn.id`, `acs.turn.triggered_by` / `acs.turn.outcome` | +| `steps/preCompact`, `steps/postCompact` | `acs.compact`, `acs.compact.complete` | `acs.compact.entry_count`, `acs.compact.triggered_by`; `postCompact` adds `acs.provenance.lineage_depth_after` (optional) | +| `steps/subagentStart`, `steps/subagentStop` | `acs.subagent`, `acs.subagent.end` | `acs.subagent.session_id`, `acs.subagent.parent_session_id`, `acs.subagent.intent_derivation` / `acs.subagent.outcome`, `acs.subagent.final_chain_hash` | +| Decision (allow/deny/modify/ask/defer) | `acs.decision` (span event) | `acs.decision`, `acs.evaluator`, `acs.reasoning` (when present), `acs.confidence` (when present) | +| `agbom/snapshot`, `agbom/changed` | `acs.agbom` | `acs.agbom.format` (`canonical`/`cyclonedx`/`spdx`/`swid`), `acs.agbom.component_count` | -**Purpose**: Captures all message exchanges in agent conversations. +When Provenance is attached to a hook payload, the resulting span MUST carry `acs.provenance.origin` as an attribute, and SHOULD carry `acs.provenance.source_id` and `acs.provenance.lineage_depth` when populated. v0.1 emits factual provenance attributes; trust classification is computed by the Guardian against local policy and is not a v0.1 span attribute (see [Specification §7](../instrument/specification.md#7-provenance)). Provenance lineage edges MAY be linked via OTel span links keyed by `provenance_id`. -**Key Attributes**: -- `role`: Who sent the message (user, agent, system) -- `content`: Message parts (text, files, data) -- `reasoning`: Agent's interpretation (for agent/system messages) -- `citations`: Source references (for agent messages) +## OCSF event classes -**When Emitted**: -- Before user input reaches the agent (`role: user`) -- After agent generates response (`role: agent`) -- When system injects messages (`role: system`) +Each ACS step is representable as an OCSF event in the class shown below (OCSF 1.5+). Required class-specific attributes are populated from the ACS payload. -**Example Scenario**: Customer asks "I need to update my payment method for account #12345". Agent must handle sensitive financial data while responding with payment update instructions. +The full mapping lives in [`trace/ocsf-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/trace/ocsf-mapping.json). See [Extending OCSF](./extend_ocsf.md) for the deeper integration guide. -**Security Risks**: Exposure of account numbers, payment details, or personal information in logs. Potential for social engineering through conversation manipulation. +| ACS step | OCSF class | Class UID | +|---|---|---| +| `steps/sessionStart` | Authentication | 3002 | +| `steps/userMessage`, `steps/agentResponse` | Application Activity | 6002 | +| `steps/toolCallRequest`, `steps/toolCallResult` | Process Activity | 1007 | +| `steps/knowledgeRetrieval`, `steps/memoryStore`, `steps/memoryContextRetrieval` | Datastore Activity | 6005 | +| `steps/preCompact`, `steps/postCompact` | Datastore Activity | 6005 (compaction subtype) | +| `steps/subagentStart`, `steps/subagentStop` | Authentication | 3002 (logon/logoff for the subagent's session) | +| `steps/turnStart`, `steps/turnEnd` | Application Activity | 6002 | +| Decision (deny/modify/ask/defer) | Detection Finding | 2004 | +| `agbom/snapshot`, `agbom/changed` | Inventory Info | 5001 | +| `steps/sessionEnd` | Authentication | 3002 (logoff) | -**Monitoring Value**: Track conversation flow, detect prompt injection attempts, audit agent responses, verify citation accuracy. +OCSF `severity_id` for decision events is set from the disposition: -**See**: [Message Object](../instrument/specification.md#33-message-object) and [steps/message method](../instrument/specification.md#45-stepsmessage) in specification. +| Disposition | `severity_id` | +|---|---| +| `allow` | 1 (Informational) | +| `modify` | 2 (Low) | +| `ask` | 3 (Medium) | +| `defer` | 3 (Medium) | +| `deny` | 4 (High) | ---- +## ACS-Trace conformance bar -### `steps/agentTrigger` +A deployment claiming **ACS-Trace** MUST: -**Purpose**: Records autonomous agent activation events. +1. Emit at least one of {OTel, OCSF} for every ACS step the deployment supports, with the required attributes populated. +2. Record every decision as a Trace event carrying the disposition, evaluator identity, and reasoning when present. +3. Carry provenance facts forward from the hook payload onto the Trace event so audit replay reconstructs the policy decision without consulting Guardian state separately. -**Key Attributes**: -- `trigger.type`: Always "autonomous" for non-user triggers -- `trigger.event`: Source event (email, slack, scheduled task) -- `content`: Extracted trigger content - -**When Emitted**: When agent activates from external events (not user messages). - -**Example Scenario**: Email from customer about fraudulent charges triggers support agent to initiate security protocols and contact fraud prevention team. - -**Security Risks**: Unauthorized agent activation, escalation of privileges through trigger manipulation, exposure of sensitive customer data in trigger content. - -**Monitoring Value**: Understand agent activation patterns, track autonomous behaviors, measure response times. - -**See**: [AgentTrigger Object](../instrument/specification.md#36-agenttrigger-object) and [steps/agentTrigger method](../instrument/specification.md#41-stepsagenttrigger) in specification. - ---- - -### `steps/toolCallRequest` - -**Purpose**: Traces tool execution requests before they execute. - -**Key Attributes**: -- `toolId`: Which tool is being called -- `executionId`: Unique execution identifier -- `inputs`: Tool arguments and values -- `reasoning`: Why agent chose this tool - -**When Emitted**: After LLM decides to use a tool, before execution. - -**Example Scenario**: Support agent requests to call `update_customer_record` tool with new billing address, requiring access to customer database with write permissions. - -**Security Risks**: Unauthorized database modifications, SQL injection through tool parameters, excessive data access beyond support scope, potential for data exfiltration. - -**Monitoring Value**: Audit tool usage, enforce security policies, track resource access patterns. - -**See**: [ToolCallRequest Object](../instrument/specification.md#315-toolcallrequest-object) and [steps/toolCallRequest method](../instrument/specification.md#46-stepstoolcallrequest) in specification. - ---- - -### `steps/toolCallResult` - -**Purpose**: Captures tool execution outcomes. - -**Key Attributes**: -- `executionId`: Links to request -- `result.outputs`: Tool results -- `result.isError`: Success/failure indicator - -**When Emitted**: After tool completes execution. - -**Example Scenario**: Customer database returns updated record confirmation or error "Insufficient privileges to modify payment methods". - -**Security Risks**: Sensitive data in tool outputs (SSN, credit cards), error messages revealing system internals, successful unauthorized operations. - -**Monitoring Value**: Track tool reliability, measure execution success rates, identify failing tools. - -**See**: [ToolCallResult Object](../instrument/specification.md#4611-toolcallresult-object) and [steps/toolCallResult method](../instrument/specification.md#46-stepstoolcallresult) in specification. - ---- - -### `steps/memoryContextRetrieval` - -**Purpose**: Records when agents retrieve stored context. - -**Key Attributes**: -- `memory`: Retrieved memory items -- `reasoning`: Why this context is needed - -**When Emitted**: When agent loads historical context for current task. - -**Example Scenario**: Support agent retrieves customer's previous support tickets, including sensitive complaint details and compensation history. - -**Security Risks**: Access to historical sensitive data beyond current need, cross-customer data leakage, retention of data beyond compliance requirements. - -**Monitoring Value**: Understand context usage patterns, optimize memory systems, track knowledge application. - -**See**: [steps/memoryContextRetrieval method](../instrument/specification.md#44-stepsmemorycontextretrieval) in specification. - ---- - -### `steps/memoryStore` - -**Purpose**: Tracks when agents save information to memory. - -**Key Attributes**: -- `memory`: Information being stored -- `reasoning`: Why this should be remembered - -**When Emitted**: When agent persists information for future use. - -**Example Scenario**: Agent stores customer's new shipping preferences and fraud alert status for future interactions. - -**Security Risks**: Storing sensitive data in unencrypted memory, retention beyond legal requirements, accumulation of PII without proper controls. - -**Monitoring Value**: Track knowledge accumulation, ensure data governance, monitor storage patterns. - -**See**: [steps/memoryStore method](../instrument/specification.md#43-stepsmemorystore) in specification. - ---- - -### `steps/knowledgeRetrieval` - -**Purpose**: Monitors knowledge base and RAG queries. - -**Key Attributes**: -- `knowledgeStep.query`: Search query -- `knowledgeStep.keywords`: Search terms -- `knowledgeStep.results`: Retrieved documents with content and metadata - -**When Emitted**: When agent queries external knowledge sources. - -**Example Scenario**: Agent searches for "refund policy for premium accounts" to handle customer's refund request, potentially accessing internal pricing strategies. - -**Security Risks**: Information disclosure through broad queries, access to confidential business documents, query injection attacks. - -**Monitoring Value**: Optimize retrieval systems, track information access, measure retrieval effectiveness. - -**See**: [KnowledgeRetrievalStepParams Object](../instrument/specification.md#314-knowledgeretrievalstepparams-object) and [steps/knowledgeRetrieval method](../instrument/specification.md#42-stepsknowledgeretrieval) in specification. - ---- - -### `protocols/A2A` - -**Purpose**: Captures agent-to-agent communication per A2A protocol standard. - -**Key Attributes**: -- `message`: A2A protocol message content -- `reasoning`: Communication intent - -**When Emitted**: -- Before sending A2A messages (outbound monitoring) -- After receiving A2A messages (inbound monitoring) - -**Example Scenario**: Support agent escalates high-value customer complaint to specialized retention agent, sharing full customer history including purchase data and satisfaction scores. - -**Security Risks**: Lateral movement of sensitive data between agents, privilege escalation through agent collaboration, data oversharing beyond minimum necessary. - -**Monitoring Value**: Map agent collaboration networks, audit cross-agent flows, ensure protocol compliance. - -#### Message Structure -The `message` attribute contains the full, A2A-compliant JSON-RPC 2.0 payload. This allows for complete visibility into the inter-agent communication. - -**Example `protocols/A2A` Event:** -```json -{ - "jsonrpc": "2.0", - "id": 70, - "method": "protocols/A2A", - "params": { - "message": { - "jsonrpc": "2.0", - "id": 1, - "method": "message/send", - "params": { - "message": { - "role": "agent", - "parts": [ - { - "kind": "text", - "text": "High-value customer complaint. Please review and advise on retention strategy." - }, - { - "kind": "data", - "data": { - "customerId": "CUST-001", - "caseId": "CASE-987", - "history": { - "purchaseValue": 50000, - "satisfactionScore": 2.5 - } - } - } - ] - } - } - }, - "reasoning": "Escalating high-value customer complaint to retention agent." - } -} -``` - -**See**: For more detailed examples, including handling of sensitive data, see the [A2A Extension Guide](../instrument/extend_a2a.md). - ---- - -### `protocols/MCP` - -**Purpose**: Traces Model Context Protocol interactions. - -**Key Attributes**: -- `message`: MCP protocol message content -- `reasoning`: Interaction purpose - -**When Emitted**: -- Before MCP client sends to server -- After MCP server responds - -**Example Scenario**: Support agent queries CRM system through MCP server to retrieve customer's full profile, including purchase history and saved payment methods. - -**Security Risks**: Exposure of API credentials, unauthorized access to backend systems, data exfiltration through MCP channels. - -**Monitoring Value**: Monitor external integrations, track MCP tool usage, audit data access. - -#### Message Structure -The `message` attribute contains the full, MCP-compliant JSON-RPC 2.0 payload. This provides a complete record of interactions with external tools and data sources via MCP. - -**Example `protocols/MCP` Event:** -```json -{ - "jsonrpc": "2.0", - "id": 70, - "method": "protocols/MCP", - "params": { - "message": { - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "name": "crm/getCustomerProfile", - "arguments": { - "customerId": "CUST-001" - } - } - }, - "reasoning": "Retrieving full customer profile from CRM to address support query." - } -} -``` - -**See**: For more detailed examples, including data masking and policy enforcement, see the [MCP Extension Guide](../instrument/extend_mcp.md). - ---- - -### `ping` - -**Purpose**: System health and connectivity checks. - -**Key Attributes**: -- `timestamp`: Check time -- `timeout`: Response deadline -- Response includes `status`, `version` - -**When Emitted**: During periodic health checks between agent and guardian. - -**Example Scenario**: Customer support agent verifies guardian agent availability before processing sensitive refund request. - -**Security Risks**: Guardian bypass attempts during connectivity issues, unmonitored agent operations during guardian downtime. - -**Monitoring Value**: Track system uptime, detect connectivity issues, measure latency. - -**See**: [ping method](../instrument/specification.md#49-ping) in specification. - -## Event Context - -Every event includes rich context through the `StepContext` object: - -- **Agent Information**: Identity, version, provider, available tools -- **Session Details**: Unique session and turn identifiers -- **Timing**: ISO 8601 timestamps for correlation -- **User Context**: User identity and organization (when applicable) - -**See**: [StepContext Object](../instrument/specification.md#38-stepcontext-object) for complete structure. - -## Decision Events - -Every ASOP request receives a decision response from the guardian agent: - -### Decision Types - -- **`allow`**: Action proceeds unchanged -- **`deny`**: Action blocked with explanation -- **`modify`**: Action altered with new parameters - -### Decision Attributes - -- `decision`: The decision type -- `reasoning`: Detailed explanation of the decision -- `reasonCode`: Structured codes for programmatic handling -- `message`: Human-readable summary -- `modifiedRequest`: Altered request (only for modify decisions) - -**Example Flow**: - -1. Support agent requests customer database write access for address update -2. Guardian evaluates against data access policy and customer consent -3. Returns "modify" decision limiting update to shipping address only -4. Agent proceeds with restricted field access - -**See**: [ASOPSuccessResult Object](../instrument/specification.md#511-asopsuccessresult-object) for complete decision structure. - -## Event Relationships - -### Session and Turn Structure - -- **Session**: Complete agent interaction lifecycle from activation to completion -- **Turn**: Single request-response cycle within a session -- **Step**: Individual action within a turn (each event is a step) - -## Error Events - -ASOP uses standard JSON-RPC 2.0 error codes: - -| Code | Error Type | Description | -|------|------------|-------------| -| -32700 | Parse error | Malformed JSON | -| -32600 | Invalid Request | Invalid JSON-RPC structure | -| -32601 | Method not found | Unknown event method | -| -32602 | Invalid params | Missing or invalid parameters | -| -32603 | Internal error | Server-side failure | - -**See**: [Error Handling](../instrument/specification.md#6-error-handling) for complete error specifications. - -## Next Steps - -1. **Review the full protocol specification** at [ASOP Protocol Specification](../instrument/specification.md) -2. **Implement event streaming** in your monitoring infrastructure -3. **Build dashboards** for agent behavior visibility -4. **Set up alerting** on critical event patterns -5. **Create audit trails** for compliance requirements +Trace events MUST NOT block enforcement — failure of the Trace sink MUST NOT change the disposition returned to the Observed Agent. Deployments that do not claim ACS-Trace SHOULD still emit Trace events where feasible; the vocabulary is normative regardless of profile claim. diff --git a/docs/spec/trace/extend_ocsf.md b/docs/spec/trace/extend_ocsf.md index e62fe6f..e765f18 100644 --- a/docs/spec/trace/extend_ocsf.md +++ b/docs/spec/trace/extend_ocsf.md @@ -1,41 +1,46 @@ -# ACS tracing with OCSF +# Extending OCSF -The Open Cybersecurity Schema Framework (OCSF) integration enables standardized security event logging for AI agent activities, making them compatible with existing SIEM and security monitoring tools. +The Open Cybersecurity Schema Framework (OCSF) integration enables standardized security-event logging for AI agent activity. ACS-shaped events drop directly into existing SIEM pipelines without bespoke parsers. -## Overview +ACS events map to OCSF 1.5+ event classes. The normative class assignments and the disposition → `severity_id` mapping live on the [Trace Events](./events.md) page; the machine-readable mapping is at [`trace/ocsf-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/trace/ocsf-mapping.json). This page describes how to assemble the events themselves and provides representative wire examples. -ACS maps agent activities to OCSF event classes, providing: +## Class assignments at a glance -- Standardized security event format -- MCP & A2A Support out of the box -- Unified view of agent and traditional security events -- Compliance-ready trace trails +| ACS step | OCSF class | Class UID | +|---|---|---| +| `steps/sessionStart`, `steps/sessionEnd`, `steps/subagentStart`, `steps/subagentStop` | Authentication | 3002 | +| `steps/userMessage`, `steps/agentResponse`, `steps/turnStart`, `steps/turnEnd` | Application Activity | 6002 | +| `steps/toolCallRequest`, `steps/toolCallResult` | Process Activity | 1007 | +| `steps/knowledgeRetrieval`, `steps/memoryStore`, `steps/memoryContextRetrieval`, `steps/preCompact`, `steps/postCompact` | Datastore Activity | 6005 | +| Decision (deny/modify/ask/defer) | Detection Finding | 2004 | +| `agbom/snapshot`, `agbom/changed` | Inventory Info | 5001 | -## Event Mapping +## Severity mapping -### Agent Activity Events +| Disposition | `severity_id` | +|---|---| +| `allow` | 1 (Informational) | +| `modify` | 2 (Low) | +| `ask` | 3 (Medium) | +| `defer` | 3 (Medium) | +| `deny` | 4 (High) | -ACS extends OCSF's API Activity class (6003) for agent-specific events. - -Here's a basic example: +## Example: tool call as Process Activity (1007) ```json { - "category_uid": 6, - "category_name": "Application Activity", - "class_uid": 6003, - "class_name": "API Activity", + "category_uid": 1, + "category_name": "System Activity", + "class_uid": 1007, + "class_name": "Process Activity", "activity_id": 1, - "activity_name": "Agent Tool Use", + "activity_name": "Launch", "time": 1706550000000, - "type_uid": 600301, + "type_uid": 100701, "severity_id": 1, "metadata": { - "version": "1.0.0", - "product": { - "name": "ACS Security Layer", - "vendor_name": "ACS" - } + "version": "1.5.0", + "product": { "name": "ACS Guardian", "vendor_name": "ACS" } }, "actor": { "user": { @@ -43,291 +48,99 @@ Here's a basic example: "name": "CustomerServiceAgent", "type_id": 99, "type": "AI Agent" - } - }, - "api": { - "service": { - "name": "database_mcp_server", - "version": "1.0.0" }, - "operation": "tools/call" + "session": { "uid": "session-789" } }, - "src_endpoint": { - "type_id": 99, - "name": "AI Agent Endpoint", - "hostname": "agent-service.internal" + "process": { + "name": "database_query", + "uid": "exec-123", + "cmd_line": "tools/call:database_query" }, - "osint": [], "unmapped": { "acs": { - "tool_call": { - "name": "database_query", - "arguments": { - "query": "SELECT * FROM customers WHERE id = ?" - } - }, - "context": { - "agent": { - "id": "agent-123", - "name": "CustomerServiceAgent", - "version": "1.0.0", - "provider": { - "name": "ACS", - "url": "https://example.acs" - } - }, - "session": { - "id": "session-789" - }, - "model": { - "id": "gpt-4", - "provider": { - "name": "OpenAI" - } - } - }, - "step": { - "id": "step-abc", - "type": "toolCall", - "turn_id": "turn-456", - "reasoning": "User requested customer information" - } + "step": { "id": "step-abc", "type": "toolCallRequest", "turn_id": "turn-456" }, + "tool": { "id": "database_query", "execution_id": "exec-123", "capability": "datastore.read" }, + "provenance": [ + { "provenance_id": "p1", "origin": "user_input", "source_id": "user-12345" } + ] } } } ``` -#### Agent with Tool Execution Example: +## Example: deny decision as Detection Finding (2004) ```json { - "category_uid": 6, - "category_name": "Application Activity", - "class_uid": 6003, - "class_name": "API Activity", + "category_uid": 2, + "category_name": "Findings", + "class_uid": 2004, + "class_name": "Detection Finding", "activity_id": 1, - "activity_name": "Tool Execution", - "time": 1706550000000, - "type_uid": 600301, - "severity_id": 1, + "activity_name": "Create", + "time": 1706550000050, + "severity_id": 4, "status_id": 1, - "status": "Success", - "metadata": { - "version": "1.0.0", - "product": { - "name": "ACS Security Layer", - "vendor_name": "ACS" - }, - "correlation_uid": "exec-123" - }, - "actor": { - "user": { - "uid": "agent-123", - "name": "CustomerServiceAgent", - "type_id": 99, - "type": "AI Agent" - }, - "session": { - "uid": "session-789" - } - }, - "api": { - "service": { - "name": "database_mcp_server", - "version": "1.0.0" - }, - "operation": "database_query", - "response": { - "code": 200, - "message": "Query executed successfully" - } - }, - "src_endpoint": { - "type_id": 99, - "name": "AI Agent Endpoint", - "hostname": "agent-service.internal", - "ip": "10.0.1.50" - }, - "dst_endpoint": { - "type_id": 1, - "name": "Database Server", - "hostname": "db.internal", - "port": 5432 + "metadata": { "version": "1.5.0", "product": { "name": "ACS Guardian", "vendor_name": "ACS" } }, + "finding_info": { + "uid": "decision-001", + "title": "Tool call denied: outside committed Intent", + "desc": "email.send to recipient outside Intent.parsed; IBAC P-I check failed." }, - "osint": [], "unmapped": { "acs": { - "step": { - "id": "step-abc", - "type": "toolCall", - "turn_id": "turn-456", - "reasoning": "User requested customer information", - "operation": { - "type": "tool_execution", - "tool": { - "id": "database_query", - "execution_id": "exec-123", - "inputs": [ - { - "name": "query", - "value": "SELECT * FROM customers WHERE id = ?" - } - ], - "outputs": [ - { - "kind": "text", - "text": "Query executed successfully" - } - ], - "is_error": false - } - } - }, - "context": { - "agent": { - "id": "agent-123", - "name": "CustomerServiceAgent", - "version": "1.0.0", - "provider": { - "name": "ACS", - "url": "https://example.acs" - } - }, - "model": { - "id": "gpt-4", - "provider": { - "name": "OpenAI" - } - } - } + "decision": "deny", + "evaluator": "deterministic", + "policy_references": [ + { "policy_id": "acme-ibac-v1", "rule_id": "email-send-recipient-not-in-intent" } + ], + "reason_codes": ["ibac_capability_mismatch"], + "cited_provenance_ids": ["p1"], + "session_id": "session-789", + "step_id": "step-abc" } } } ``` -#### Multi-Agent Workflow Example +## Example: AgBOM snapshot as Inventory Info (5001) ```json { - "category_uid": 6, - "class_uid": 6003, + "category_uid": 5, + "category_name": "Discovery", + "class_uid": 5001, + "class_name": "Inventory Info", "activity_id": 1, - "activity_name": "Agent Request", - "time": 1706550000000, - "type_uid": 600301, + "activity_name": "Log", + "time": 1706549900000, "severity_id": 1, - "metadata": { - "version": "1.0.0", - "product": { - "name": "ACS Security Layer", - "vendor_name": "ACS" - }, - "correlation_uid": "4bf92f3577b34da6a3ce929d0e0e4736" - }, - "actor": { - "user": { - "uid": "planner-123", - "name": "PlannerAgent", - "type_id": 99, - "type": "AI Agent" - } - }, - "api": { - "operation": "task_delegation", - "service": { - "name": "agent_orchestrator", - "version": "1.0.0" - } - }, - "src_endpoint": { - "type_id": 99, - "name": "PlannerAgent", - "hostname": "planner.agents.internal" - }, - "dst_endpoint": { - "type_id": 99, - "name": "ExecutorAgent", - "hostname": "executor.agents.internal" - }, - "osint": [], - "trace": { - "uid": "4bf92f3577b34da6a3ce929d0e0e4736", - "span": { - "uid": "00f067aa0ba902b7", - "start_time": 1706550000000, - "end_time": 1706550001000 - } + "metadata": { "version": "1.5.0" }, + "device": { + "uid": "agent-123", + "name": "CustomerServiceAgent", + "type": "AI Agent" }, "unmapped": { "acs": { - "agent_context": { - "agent": { - "id": "planner-123", - "name": "PlannerAgent", - "version": "1.0.0", - "provider": { - "name": "ACS", - "url": "https://example.acs" - } - }, - "session": { - "id": "collab-789" - }, - "turn": { - "id": "turn-456" - }, - "step": { - "id": "step-abc", - "type": "protocolMessage" - }, - "model": { - "id": "gpt-4", - "provider": { - "name": "OpenAI" - } - }, - "reasoning": "Task requires specialized database access" + "agbom": { + "format": "canonical", + "component_count": 7, + "components": [ + { "type": "model", "id": "gpt-4o", "provider": "OpenAI" }, + { "type": "mcp_server", "id": "db-mcp", "endpoint": "https://mcp.internal/db" }, + { "type": "tool", "id": "database_query", "capability": "datastore.read" } + ] } } } } ``` +## Implementation notes -## Key Features - -### 1. Standardized Event Format -- Consistent structure across all agent activities -- Compatible with existing security tools -- Extensible for custom agent attributes - -### 2. Agent Tool Use Support -- Enables AI agent tool use monitoring -- Extends tool use trace and explainability -- Support MCP tool and resource access tracing - -### 3. Compliance Support -- Trace-ready event logging -- Traceable agent activities -- Policy violation tracking - -### 4. Multi-Agent Support -- Correlation across agent interactions -- Distributed tracing support -- Hierarchical event relationships - -## Read Next - -For detailed implementation examples, including: -- Code samples -- Advanced usage patterns -- SIEM integration examples -- Custom field documentation -- Multi-agent workflows -- Validation and error handling - -Please refer to the [Implementation Examples](./OCSF/implementation_examples.md) document. +- **`actor.user.type_id: 99`** is the OCSF "Other" sentinel; pair with `actor.user.type: "AI Agent"` so SIEM filters can distinguish agent identities from human ones. +- **`unmapped.acs`** carries ACS-specific facts that don't have direct OCSF equivalents in v1.5. The structure mirrors the request envelope's `payload` plus the decision envelope's `policy_data` / `reason_codes`. Backends that index `unmapped` keep this fully searchable. +- **Provenance** flows onto OCSF events under `unmapped.acs.provenance`. Trust classification is omitted on the wire for v0.1; backends that compute trust apply it as an enrichment step. -- [OCSF Schema Documentation](https://schema.ocsf.io/) -- [py-ocsf-models Repository](https://github.com/prowler-cloud/py-ocsf-models) -- [OCSF Examples](https://github.com/ocsf/examples) +For more worked examples — including A2A and MCP wrapped events — see [Implementation examples](./OCSF/implementation_examples.md). diff --git a/docs/spec/trace/extend_opentelemetry.md b/docs/spec/trace/extend_opentelemetry.md index 3155100..05547e0 100644 --- a/docs/spec/trace/extend_opentelemetry.md +++ b/docs/spec/trace/extend_opentelemetry.md @@ -1,37 +1,34 @@ -# ACS tracing with Open Telemetry - -To thoroughly trace an AI agent, it is crucial to connect traces and events in a manner that accurately reflects the agent's atomic actions and its broader units of logical operation. This provides a transparent, step-by-step visualization of how an agent processes information, arrives at decisions, and executes tasks. The ACS schema offers a structured framework for defining these interactions, which can then be effectively mapped to OpenTelemetry concepts. - -- **Mapping ACS Steps to OpenTelemetry Spans**: Each distinct step defined within ACS can be directly correlated with an OpenTelemetry span. For instance: - - - `steps/message`: A span can be initiated when a user message is processed or when an agent generates a message. Attributes for this span would ideally include the message role, its content (potentially summarized or hashed to protect Personally Identifiable Information (PII)), and relevant IDs. - - - `steps/agentTrigger`: A dedicated span for an autonomous agent trigger, detailing the type of event and the content that prompted the agent's action. - - - `steps/toolCallRequest` and `steps/toolCallResult`: These map intuitively to spans encapsulating tool invocations. The `toolCallRequest` span should capture the `toolId`, `inputs`, and the `executionId`. The corresponding `toolCallResult` span would include the `executionId` and the `result` (comprising content and error status). - - - `steps/memoryRetrieval` and `steps/memoryRetrievalResult`: Spans designed for instances when an agent queries its memory and processes the retrieved results. Attributes should encompass memory type, the query itself, and the content of the retrieved memory (again, with due consideration for PII). - - - `steps/knowledgeRetrieval` and `steps/knowledgeRetrievalResult`: Analogous to memory operations, these spans are for querying and retrieving information from knowledge bases, capturing the query, any keywords used, and the results obtained. - - - `steps/knowledgeStore`: A span representing the action of an agent storing information into a knowledge base. - -- **Hierarchical Spans for Logical Operations**: - - - A top-level span (e.g., named `agent.run` or `session`) can encompass the entirety of an interaction or a specific task undertaken by the agent. - - - Within this primary span, child spans can represent major logical phases such as `agent.plan` or individual turns within a conversation. - - - Each "turn" (which can be identified by a `turnId` as per ACS ) can itself be a parent span. - - - Individual "steps" (identifiable by a `stepId` in ACS ) occurring within a turn, such as an LLM call followed by a tool call, then become child spans under the respective turn span. This structure aligns well with the `RequestContext` defined in ACS, which includes `agent`, `session`, `turnId`, and `stepId`. - -- **Enriching Spans with Attributes from Agent Logic**: - - - **Reasoning**: Many steps in the ASOP schema incorporate a `reasoning` field. This critical piece of information, which elucidates the agent's rationale for a particular action, should be added as a custom attribute to the corresponding OpenTelemetry span (e.g., `agent.thought`, `agent.reasoning`). - - - **Inputs and Outputs**: For every atomic action (such as an LLM call, a tool call, or memory access), the precise inputs and outputs must be attached as span attributes. For LLM calls, this includes the prompt, model parameters (like temperature and max tokens), and the generated completion. For tool calls, it involves the tool name, input arguments, and output data. Sensitive data contained within these attributes may necessitate hashing, truncation, or redaction in accordance with prevailing privacy policies. - - - **Agent and Model Information**: Specific details about the agent (`agent.id`, `agent.name`, `agent.version`) and the LLM (`llm.model.name`, `llm.provider.name`) involved in each step should be included either as resource attributes or span attributes to provide comprehensive context. - - - **Timestamps**: Accurate timestamps marking the beginning and end of each operation are fundamental for performance analysis and correctly sequencing events. +# Extending OpenTelemetry + +ACS reuses OpenTelemetry as the primary observability transport. Each ACS step becomes a span; each Guardian decision becomes a span event on the parent step span. The result is an end-to-end view of agent behavior — reasoning, retrieval, tool calls, decisions — already shaped to fit existing OTel-based tooling. + +The normative span-name and required-attribute table is on the [Trace Events](./events.md) page. The machine-readable mapping lives at [`trace/otel-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/trace/otel-mapping.json). This page describes how to *use* the mapping in practice. + +## Span hierarchy + +A typical agent session produces a tree of spans: + +- A root **session** span (`acs.session`) opened by `steps/sessionStart` and closed by `steps/sessionEnd`. Carries `acs.session.id` and (when set) `acs.tenant_id`. +- One **turn** span (`acs.turn`) per agent turn, opened by `steps/turnStart` and closed by `steps/turnEnd`. Carries `acs.turn.id`. Nested turns (e.g. subagent turns inside a parent turn awaiting `subagentStop`) record `acs.turn.parent_id`. +- One **step** span per `steps/*` hook, parented to the enclosing turn (or session when the step is session-level: `agentTrigger`, `agbom/*` outside a turn). +- Each **decision** is a span event on the step span carrying `acs.decision`, `acs.evaluator` (`deterministic` / `agent` / `composite`), `acs.reasoning` (when present), and `acs.confidence` (when present). Decisions are not separate spans because the verdict and the action it gates share a parent. + +This hierarchy is deterministic — implementations that follow the mapping produce comparable traces across vendors and Guardians. + +## Provenance attributes + +When a hook payload carries Provenance, the resulting span MUST carry `acs.provenance.origin` and SHOULD carry `acs.provenance.source_id` and `acs.provenance.lineage_depth`. The set of provenance ids cited by a Guardian decision (the response envelope's `cited_provenance_ids`) MAY be expressed as OTel span links keyed by `provenance_id`, letting tools that index span links reconstruct the lineage graph without parsing payloads. + +v0.1 emits factual provenance attributes only. Trust classification is computed by the Guardian against local policy and is not a v0.1 span attribute. + +## Sensitive data + +Attributes can carry user prompts, tool arguments, and retrieved knowledge. Implementations SHOULD apply the deployment's redaction or hashing policy at attribute-emit time rather than relying on backend-side scrubbing. The Guardian's `modifications.redactions` payload is the natural input to the policy: the same list that drives content rewriting drives attribute redaction. + +## Transport + +ACS does not redefine a trace transport. Implementations emit OTLP/gRPC or OTLP/HTTP to existing backends. The Guardian's handshake `trace_emission` field MAY advertise an OTLP collector endpoint; when set, the Observed Agent SHOULD route ACS-shaped trace traffic there in addition to (or instead of) its default collector. + +## Failure isolation + +Trace events MUST NOT block enforcement. If the Trace sink is unreachable or returns an error, the Guardian's disposition MUST still be returned to the Observed Agent. The agent's hot path is enforcement; observability is best-effort. diff --git a/docs/topics/ACS_in_action_example.md b/docs/topics/ACS_in_action_example.md index 3b21372..551e6f6 100644 --- a/docs/topics/ACS_in_action_example.md +++ b/docs/topics/ACS_in_action_example.md @@ -1,146 +1,264 @@ -# Illustrating ACS in Action +# ACS in Action -Please read [Core Concepts](./core_concepts.md) if you haven't already. +A worked example of an Observed Agent calling the `email.send` tool, with a Guardian Agent enforcing IBAC + FIDES policies, emitting a Trace event, and updating the AgBOM. Read [Core Concepts](./core_concepts.md) first if you haven't. -## Overall process +## Sequence -The following sequence diagram describes an example of MCP tool call request by an Observed Agent. -Instrumented with ACS the Observed Agent communicates with a Guardian Agent. +``` +Observed Agent Guardian Agent Trace sink (OTLP/SIEM) +══════════════ ══════════════ ══════════════════════ + │ │ │ + │ 1. handshake/hello │ │ + ├─────────────────────────>│ │ + │ 2. ServerHello │ │ + │<─────────────────────────┤ │ + │ 3. agbom/snapshot │ │ + ├─────────────────────────>│ │ + │ │ 4. Trace: acs.agbom │ + │ ├─────────────────────────>│ + │ 5. allow │ │ + │<─────────────────────────┤ │ + │ 6. steps/sessionStart │ │ + ├─────────────────────────>│ 7. Open chain root │ + │ 8. allow │ │ + │<─────────────────────────┤ │ + │ 9. steps/userMessage │ │ + ├─────────────────────────>│ │ + │ 10. allow │ │ + │<─────────────────────────┤ │ + │ 11. steps/toolCallRequest│ │ + ├─────────────────────────>│ 12. Evaluate IBAC + FIDES│ + │ │ 13. Trace: acs.decision │ + │ ├─────────────────────────>│ + │ 14. allow │ │ + │<─────────────────────────┤ │ + │ 15. Execute tool │ │ + │ 16. steps/toolCallResult │ │ + ├─────────────────────────>│ │ + │ 17. allow │ │ + │<─────────────────────────┤ │ +``` -![Sequence Diagram](../assets/sequence_diagram.png "Sequence Diagram") +## The decision point: `steps/toolCallRequest` -The Guardian Agent has 3 roles: +The Observed Agent has been asked by a user to "summarize Project X status and email it to my manager." The agent has retrieved the status (untrusted, from a knowledge base), composed a body via the LLM (agent_generated, derived from the retrieval), and is about to call `email.send`. The Guardian must decide. -1. It should permit, deny or modify the request and send back its verdict by ACS -2. Sending a trace of the request for observability purpose using OpenTelemetry or OCSF -3. Update it's bill-of-material with the new tool using CycloneDX, SWID or SPDX +This example shows a deployment that elects to populate the OPTIONAL `trust` field on the wire. An equally-conformant v0.1 deployment would omit `trust` from the Provenance objects and have the Guardian derive the same classification from `origin` + `source_id` against local policy. -## The Step by Step process +### Request -### Step 1: Agent MCP Tool Call -```python -# add here the MCP tool call format -``` -### Step 2: Agent ACS Request sending -```python -# add here the ACS Request with the MCP tool call -``` -### Step 3: Guardian Agent sending a trace of the MCP Tool Call -```python -# add here the OpenTelemetry message example -``` -### Step 4: Guardian Agent Applying Policy Enforcement -```python - -# This is a code snippet example for security policy that allows only MCP servers from a given list - -def is_known_mcp_server(ip): - """ - Check if the provided IP address belongs to a known MCP CIDR block. - """ - try: - ip_obj = ipaddress.ip_address(ip) - return any(ip_obj in ipaddress.ip_network(cidr) for cidr in KNOWN_MCP_SERVERS) - except ValueError: - return False - -@app.before_request -def restrict_to_mcp_servers(): - """ - Middleware to restrict incoming requests to only those originating from known MCP servers. - """ - client_ip = request.remote_addr - if not is_known_mcp_server(client_ip): - abort(403, description="Access denied: IP not in known MCP server list") -``` -### Step 5: Guardian Agent Sending a "Permitted" Response -```python -# add here the ACS Response +```json +{ + "jsonrpc": "2.0", + "method": "steps/toolCallRequest", + "id": "req-001", + "params": { + "acs_version": "0.1.0", + "request_id": "550e8400-e29b-41d4-a716-446655440000", + "timestamp": "2026-04-30T10:30:00Z", + "tenant_id": "acme-corp", + "metadata": { + "agent_id": "cursor-agent-01", + "session_id": "abc-123", + "turn_id": "t-7", + "platform": "cursor" + }, + "payload": { + "tool": "email.send", + "arguments": { + "recipient": { + "value": "manager@company.com", + "provenance": { + "provenance_id": "p1", + "origin": "user_input", + "trust": "trusted", + "source_id": "user-12345", + "derived_from": [] + } + }, + "body": { + "value": "Project X summary: ...", + "provenance": { + "provenance_id": "p3", + "origin": "agent_generated", + "trust": "untrusted", + "source_id": "llm-gpt-4", + "derived_from": ["p2"] + } + } + } + } + } +} ``` -### Step 6: Guardian Agent Sending an updated BOM -The Guardian Agent update its BOM and send an updated file in CycloneDx format +### Decision -```python +The Guardian's deterministic layer evaluates against IBAC (does `email.send` to `manager@company.com` fall inside `Intent.parsed`?) and FIDES (does the trusted-recipient + possibly-tainted-body combination satisfy the P-F flow check?). Both pass. + +```json { - "bomFormat": "CycloneDX", - "specVersion": "1.6", - "version": 1, - "metadata": { - "timestamp": "2025-05-19T12:00:00Z", - "tools": [ - { "name": "cyclonedx-python-lib", "version": "6.2.1" } + "jsonrpc": "2.0", + "id": "req-001", + "result": { + "type": "final", + "acs_version": "0.1.0", + "request_id": "550e8400-e29b-41d4-a716-446655440000", + "decision": "allow", + "reasoning": "Recipient is trusted (user_input); body lineage is untrusted but the trusted recipient + possibly-tainted body combination is permitted by FIDES P-F under acme-baseline-v1.", + "reason_codes": ["fides_p_f_check_passed", "ibac_capability_match"], + "policy_references": [ + { "policy_id": "acme-baseline-v1", "rule_id": "allow-trusted-recipients" }, + { "policy_id": "acme-ibac-v1", "rule_id": "email-send-in-intent" } ], - "authors": [ - { "name": "AgentOps Team", "email": "agentops@example.com" } - ] - }, - "components": [ - { - "type": "service", - "name": "finance-summary-agent", - "version": "1.2.3", - "bom-ref": "urn:agent:finance-summary-agent", - "properties": [ - { "name": "a2aCardUrl", "value": "https://agent.example.com/.well-known/agent.json" }, - { "name": "languageRuntime", "value": "Python 3.10.9" }, - { "name": "environment.os", "value": "Ubuntu 22.04" }, - { "name": "environment.architecture", "value": "x86_64" }, - { "name": "model", "value": "gpt-4-32k" }, - { "name": "modelContextWindow", "value": "32768" }, - { "name": "memoryBackend", "value": "Pinecone" }, - { "name": "memoryLimitMB", "value": "2048" }, - { "name": "compliance", "value": "SOC2, GDPR" } - ] + "policy_data": { + "fides": { "recipient_trust": "trusted", "body_trust": "untrusted", "p_f_passed": true }, + "ibac": { "matched_capability": { "tool": "email.send", "resource": "manager@company.com" } } }, - { - "type": "tool", - "name": "WebSearchAPI", - "version": "v1", - "bom-ref": "urn:tool:websearchapi", - "properties": [ - { "name": "description", "value": "External web search via Bing API" }, - { "name": "endpoint", "value": "https://api.bing.microsoft.com/v7.0/search" }, - { "name": "auth", "value": "API key" }, - { "name": "scope", "value": "read-only" }, - { "name": "timeoutMs", "value": "3000" } - ] - }, - { - "type": "tool", - "name": "PythonREPL", - "bom-ref": "urn:tool:pythonrepl", - "properties": [ - { "name": "description", "value": "Sandboxed Python evaluator" }, - { "name": "sandbox", "value": "true" }, - { "name": "memoryLimitMB", "value": "128" } - ] + "cited_provenance_ids": ["p1", "p3"], + "metadata": { + "evaluator": "deterministic", + "evaluator_version": "opa-0.65", + "evaluation_duration_ms": 12 + } + } +} +``` + +## What the Guardian also does + +The decision above is one of three concurrent obligations: + +1. **Permit/deny/modify** — return the decision envelope to the Observed Agent (above). +2. **Trace** — emit an OTel span event `acs.decision` on the parent `gen_ai.tool.call` span, plus an OCSF Detection Finding (2004) when the decision is non-`allow`. See [Trace Events](../spec/trace/events.md). +3. **AgBOM** — `agbom/snapshot` was emitted earlier in the session; if the agent has registered new components since, `agbom/changed` is emitted before any content-bearing hook continues. See [Inspect](../spec/inspect/README.md). + +## What different paradigms cite + +The same wire format supports four published agent-security architectures and their compositions. Each cites different primitives in `policy_data` and `cited_provenance_ids`, but the envelope shape is identical. Below: one denial-shaped example per paradigm. + +### IBAC — Intent-based authorization + +User's committed Intent: `["summarize Project X"]`. The agent attempts `email.send` to a recipient outside that capability list. IBAC denies because the action is not in `Intent.parsed`. The user can extend `Intent.parsed` via the [ASK flow](../spec/instrument/specification.md#91-intent-extension-via-ask-normative) if deployment policy permits. + +```json +{ + "decision": "deny", + "reasoning": "Tool call email.send falls outside Intent.parsed for session abc-123.", + "reason_codes": ["ibac_capability_mismatch"], + "policy_references": [ + { "policy_id": "acme-ibac-v1", "rule_id": "tool-call-must-be-in-intent" } + ], + "policy_data": { + "ibac": { + "requested_capability": { "tool": "email.send", "resource": "manager@company.com" }, + "intent_parsed": ["summarize.read"], + "closest_match_in_intent": null + } + }, + "cited_provenance_ids": ["p1"] +} +``` + +### FIDES — Information flow control + +A second tool call later in the same session: `email.send` with a `recipient` derived from a tool lookup (`origin: tool_output`, lineage includes untrusted retrieved content) and a body derived from the same retrieval. FIDES's P-T (planner-taint) check fails: a consequential outbound action depends on a fully-untrusted lineage with no trusted re-grounding. + +```json +{ + "decision": "deny", + "reasoning": "Outbound email.send recipient and body both derive from untrusted retrieval (provenance_id=p2); FIDES P-T denies under acme-baseline-v1.", + "reason_codes": ["fides_p_t_failed", "untrusted_recipient_derivation"], + "policy_references": [ + { "policy_id": "acme-baseline-v1", "rule_id": "no-untrusted-recipient-derivation" } + ], + "policy_data": { + "fides": { + "p_t_passed": false, + "violating_argument_path": ["recipient"], + "lineage_origins": ["retrieved", "agent_generated"], + "earliest_untrusted_provenance_id": "p2" } + }, + "cited_provenance_ids": ["p2", "p4", "p5"] +} +``` + +### CaMeL — Program synthesis + +A calendar agent: privileged LLM emits a program `schedule_meeting(attendees=USER_TEAM, time=NEXT_TUESDAY)`. The unprivileged LLM fills `attendees` from a forwarded calendar invite (which carried a hidden injection) instead of from the user's stated team list. CaMeL denies because the per-argument `derived_from` graph shows a consequential argument flowing from untrusted `retrieved` data along a path the program's capability declarations do not authorize. + +```json +{ + "decision": "deny", + "reasoning": "Argument 'attendees' derives from retrieved content (provenance_id=p7) along a path the synthesized program's capability set forbids.", + "reason_codes": ["camel_argument_dependency_violation"], + "policy_references": [ + { "policy_id": "acme-camel-v1", "rule_id": "consequential-args-must-trace-to-trusted-input" } ], - "dependencies": [ - { - "ref": "urn:agent:finance-summary-agent", - "dependsOn": [ - "urn:tool:websearchapi", - "urn:tool:pythonrepl" - ] + "policy_data": { + "camel": { + "argument_path": ["attendees"], + "argument_lineage": ["p7", "p6"], + "expected_lineage_root": { "origin": "user_input", "provenance_id": "p1" }, + "actual_lineage_root": { "origin": "retrieved", "source_id": "calendar-invite-2026-04-29" } } + }, + "cited_provenance_ids": ["p1", "p6", "p7"] +} +``` + +### AARM — Cumulative-context tracking + +Much later in a session that has touched untrusted email content, the agent attempts `payment.transfer`. The action's own arguments may look clean, but AARM's lookback against the session's accumulated `provenance_summary` shows untrusted retrieval has entered without an intervening trusted re-grounding. The deployment policy denies consequential financial actions in any tainted session window. + +```json +{ + "decision": "deny", + "reasoning": "Session abc-123 has processed retrieved content from step s-14 onward without trusted re-grounding; payment.transfer is consequential per acme-aarm-v1.", + "reason_codes": ["aarm_cumulative_taint", "consequential_action_in_tainted_window"], + "policy_references": [ + { "policy_id": "acme-aarm-v1", "rule_id": "no-consequential-finance-after-untrusted-retrieval" } ], - "signatures": [ - { - "value": "", - "keyId": "agent-signing-key" + "policy_data": { + "aarm": { + "lookback_window": "session", + "earliest_untrusted_step_id": "s-14", + "untrusted_origins_seen": ["retrieved", "tool_output"], + "entry_count_by_origin": { "user_input": 4, "retrieved": 2, "tool_output": 11, "agent_generated": 9 }, + "trusted_regrounding_since": null } - ] + }, + "cited_provenance_ids": ["p_s14", "p_s17", "p_s23"] } ``` -### Step 7: Agent Sending the Tool Call Request to the MCP server -```python -## add here the MCP standard tool call + +### Composition: IBAC outer + FIDES inner + +In multi-paradigm deployments a single decision MAY cite all firing paradigms — `policy_references` carries one entry per paradigm, `reason_codes` aggregates, `policy_data` is keyed by paradigm. A request that fails IBAC's capability check *and* FIDES's flow check denies once with both contributions: + +```json +{ + "decision": "deny", + "reasoning": "Action outside Intent.parsed (IBAC) and arguments derived from untrusted retrieval (FIDES P-T); either alone would deny.", + "reason_codes": ["ibac_capability_mismatch", "fides_p_t_failed"], + "policy_references": [ + { "policy_id": "acme-ibac-v1", "rule_id": "tool-call-must-be-in-intent" }, + { "policy_id": "acme-fides-v1", "rule_id": "p-t-blocks-untrusted-derivation" } + ], + "policy_data": { + "ibac": { "requested_capability": { "tool": "email.send" }, "intent_parsed": ["summarize.read"] }, + "fides": { "p_t_passed": false, "violating_argument_path": ["body"] } + }, + "cited_provenance_ids": ["p1", "p2", "p3"] +} ``` +Audit replay walks `policy_references` to reconstruct each paradigm's contribution independently. + ## Read Next - [Instrument](../spec/instrument/README.md) - [Trace](../spec/trace/README.md) -- [Inspect](../spec/inspect/README.md) \ No newline at end of file +- [Inspect](../spec/inspect/README.md) +- [Conformance Profiles](../spec/conformance.md) diff --git a/docs/topics/core_concepts.md b/docs/topics/core_concepts.md index 6a44827..1b5f116 100644 --- a/docs/topics/core_concepts.md +++ b/docs/topics/core_concepts.md @@ -1,60 +1,67 @@ -# Core concepts +# Core Concepts -ACS specifies the in-line _Hooks_ and out-of-band _Events_ that an agent need to support to be considered trustworthy. -Usings these events and hooks, _Observed Agents_ can be monitored and protected by a _Guardian Agent_. +ACS specifies how an AI agent exposes its behavior so a separate **Guardian Agent** can permit, deny, or modify what the agent does — in real time, with a verifiable audit trail. The agent being monitored is an **Observed Agent**. -## Agent Control Standard +## The three pillars -To support an holistic view and security enforcement, the framework defines three components +ACS v0.1.0 organizes capabilities into three co-equal pillars: -1. **Instrument** - Observed agents provide standard hooks that can modify runtime execution via lightweight callouts -2. **Trace** - Observed agents emit comprehensive events on every runtime decision and lifecycle change -3. **Inspect** - Observed agents support requests for Agent Bill-Of-Material (AgBOM) +1. **Instrument** — real-time control points (hooks). Observed Agents send hook traffic to the Guardian; the Guardian returns one of five dispositions (`allow`, `deny`, `modify`, `ask`, `defer`). Hooks fire before actions execute, enabling preventive enforcement. See [Specification](../spec/instrument/specification.md) and [Hooks](../spec/instrument/hooks.md). +2. **Trace** — deterministic event emission. Every hook is also recordable as an OpenTelemetry span and an OCSF event. Decisions are recorded as span events on the parent step span, so the verdict and the action it gates share a parent. See [Trace Events](../spec/trace/events.md). +3. **Inspect** — queryable, dynamic Agent Bill of Materials (AgBOM). The Observed Agent declares its components — models, MCP servers, A2A peers, tools, knowledge sources, memory stores — and reports mutations. See [Inspect](../spec/inspect/README.md). -## Agent Environment Overview +A v0.1.0-conformant deployment implements **ACS-Core** (the Instrument baseline). Trace, Inspect, field-level Provenance, cryptographic signatures, and strengthened audit chains are organized as [conformance profiles](../spec/conformance.md) declared in the handshake. -An agent operates within an environment that includes interactions with several key entities and system components: +## The two parties -| Components | Description | Can be Local | Can be Remote | Related Protocols | -|--|--|--|--|--| -| User | Interface with the user, either directly or indirectly | ✓ | ✓ | | -| Trigger | Any system event that triggers the agent including notifications, recurrent triggers etc. | ✓ | ✓ | | -| Other Agents | Communication and collaboration with peer agents | ✓ | ✓ | A2A | -| Memory | Access short-term or long-term, memory resources | ✓ | ✓ | | -| Knowledge | Files, resources, RAG, and other sources | ✓ | ✓ | MCP | -| Prompts | Saved prompts used by the agent as templates for sub-tasks | ✓ | ✗ | MCP | -| API Tools | Tool calls that operate over REST API calls or function calls | ✓ | ✓ | MCP | -| OS Tools | Tool calls that operate via Operating System calls or direct keyboard and mouse manipulation (CUA agents) | ✓ | ✗ | | -| LLM | Direct access to LLMs either for sub-tasks or reasoning | ✓ | ✓ | | +- **Observed Agent** — the LLM-backed system being monitored. Sends hook traffic. Enforces decisions. +- **Guardian Agent** — the policy enforcement point. Two internal layers: + - **Deterministic layer** (OPA/Rego, Cedar): always runs first. + - **Agent layer** (LLM): invoked only when the deterministic layer's chain config delegates. OPTIONAL in v0.1; deterministic-only deployments are fully conformant. -A trustworthy agent is an Observed Agent that transparently exposes its interactions with the environment through standardized APIs to a Guardian Agent +## Vocabulary -![Agent Diagram](../assets/agent_env.png "Agent Environment Diagram") +- **Session** — scoped interaction unit, from agent activation to completion. Carries a `session_id`, an append-only audit chain, optional Intent, and a running `chain_hash` (rolling SHA-256). +- **Turn** — one end-to-end loop within a session, marked by `turnStart` and `turnEnd`. Many policies key on per-turn state ("limit tool calls per turn," "no consequential action in N turns after taint"). +- **Step** — atomic action or decision: a user message, a tool call, a memory write, a knowledge retrieval. Each step produces a `ContextEntry` in the session's audit chain. +- **Hook Response** — the Guardian's verdict: `allow`, `deny`, `modify` (proceed with changes), `ask` (escalate to an approver), `defer` (verdict not yet reachable). -## Agent Instrumentation +## Provenance -### Observed Agent Responsibilities: -An Observed Agent should ensure inspectability, traceability, and observability by: +Every data-bearing field MAY carry a Provenance object: `provenance_id`, `origin` (`user_input`, `system`, `tool_output`, `retrieved`, `agent_generated`, `a2a_inbound`, `external`), `source_id`, `derived_from` (lineage). Provenance is populated by deterministic framework code at channel boundaries, never by the LLM. -- Emitting Standard Events: Every interaction with the environment must be exposed through standardized event formats. -- Standardized Tracing: Maintain a standardized trace of all interactions to support improved observability, enable a holistic multi-agent view, and facilitate historical interaction analysis. -- Instrumentation: Events should trigger hooks that allow the Guardian Agent to enforce policies. Example policies may include restricting external communication, redacting sensitive data, or enforcing compliance constraints. Based on these policies, the Guardian Agent can permit, deny, or modify the content of the interaction. -- Reactive Capabilities: The agent must be capable of responding to Guardian Agent directives, including action denials or content mutations. +v0.1 keeps trust *classification* off the mandatory wire surface: Guardians derive trust from `origin` + `source_id` against local policy. The wire format reserves an OPTIONAL `trust` enum for vendor implementations that elect to carry it; when populated, the **monotonicity rule** applies — `agent_generated` data inherits the minimum trust of its lineage. -### Guardian Agent Responsibilities: -The Guardian Agent enforces policies and enables tracing through the following: +Field-level Provenance is required under the **ACS-Provenance** profile and load-bearing for FIDES, CaMeL, and AARM-style enforcement. -- Event Instrumentation Utilization: Leverage standard event hooks to evaluate and enforce policies, responding with permit, deny, or modify instructions. -- Standardized Tracing: Maintain a consistent trace of all interactions to enhance observability, support a comprehensive view across agents, and enable detailed analysis of interaction history. -- Provide comprehensive and dynamic AgBOM: Maintain and notify an up to date list of components and dependencies for the agent environment changes such as new or updated tools, models and other components +## SessionContext and Intent -## A2A and MCP +The Guardian maintains per-session state: the audit chain, the running provenance summary, and (optionally) an Intent. The Observed Agent sends only the `session_id` and an optional `chain_hash` for verification; SessionContext lives only on the Guardian. + +**Intent** is the explicit, structured authorization for a session: `raw` (the user's request), `parsed` (a capability list), `parser_provenance`, `scope_mode`. Once an Intent is established, `Intent.parsed` MUST NOT be modifiable by the runtime LLM, by tool outputs, or by any data crossing an `untrusted` channel. Intent extension is permitted only through approver action via the ASK flow. This rule is what makes IBAC's central security claim hold: the capability set is fixed before untrusted data enters and can grow only through explicit, audited approver action. + +## Agent environment -ACS works even better when MCP and A2A are part of an Agent's environment. -It carries MCP and A2A intact, ensuring full compatibility and transparency. +| Component | Description | Local | Remote | Protocol | +|---|---|---|---|---| +| User | Direct or indirect human interface | ✓ | ✓ | — | +| Trigger | System event activating the agent (notifications, schedules, A2A inbound) | ✓ | ✓ | — | +| Other Agents | Peer agents | ✓ | ✓ | A2A | +| Memory | Short- or long-term state | ✓ | ✓ | — | +| Knowledge | Files, RAG sources, vector DBs | ✓ | ✓ | MCP | +| Prompts | Saved templates for sub-tasks | ✓ | ✗ | MCP | +| API Tools | REST or function calls | ✓ | ✓ | MCP | +| OS Tools | OS calls or keyboard/mouse manipulation (CUA agents) | ✓ | ✗ | — | +| LLM | Reasoning / sub-task model | ✓ | ✓ | — | + +![Agent environment](../assets/agent_env.png "Agent environment") + +## A2A and MCP -ACS also proposes security extensions for [MCP](../spec/instrument/extend_mcp.md) and [A2A](../spec/instrument/a2a/extend_a2a.md) for native observability support. +ACS carries MCP and A2A intact. Wrapped MCP messages flow through `protocols/MCP/*` (e.g. `protocols/MCP/tools/call`). ACS also proposes security extensions for [MCP](../spec/instrument/extend_mcp.md) and [A2A](../spec/instrument/a2a/extend_a2a.md) for native observability support. The `protocols/A2A/*` namespace is reserved in v0.1; the wrapping specification arrives in v0.2. ## Read Next - [ACS in Action](./ACS_in_action_example.md) +- [Conformance Profiles](../spec/conformance.md) +- [Specification](../spec/instrument/specification.md) diff --git a/mkdocs.yml b/mkdocs.yml index a3485c9..1e8b787 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -67,6 +67,7 @@ nav: - Core concepts: topics/core_concepts.md - ACS in Action: topics/ACS_in_action_example.md - Specification: + - Conformance Profiles: spec/conformance.md - Instrument: - Overview: spec/instrument/README.md - Supported Hooks: spec/instrument/hooks.md diff --git a/specification/ACS/acs_schema.json b/specification/ACS/acs_schema.json index 34b814a..12386fe 100644 --- a/specification/ACS/acs_schema.json +++ b/specification/ACS/acs_schema.json @@ -1,2707 +1,51 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ACS Schema", - "description": "JSON Schema for Agent Control Standard", - "version": "0.1.0", - "$defs": { - "A2AFullAgentContext":{ - "type":"object", - "title":"From", - "properties":{ - "agent": - { - "$ref":"#/$defs/Agent" - }, - "role":{ - "type":"string", - "title":"Agent Role", - "enum":["client","server"] - } - } - }, - "A2APartialAgentContext":{ - "type":"object", - "title":"To", - "properties":{ - "agent": { - "$ref":"#/$defs/A2APartialAgentDetails" - }, - "role":{ - "type":"string", - "title":"Agent Role", - "enum":["client","server"] - } - } - }, - "A2APartialAgentDetails":{ - "type":"object", - "title":"To", - "properties":{ - "agent": { - "url":{ - "type":"string", - "title":"A URL to the address the agent is hosted at." - }, - "name":{ - "type":"string", - "title":"Agent Name" - }, - "version":{ - "type":"string", - "title":"Agent Version" - }, - "identity": { - "$ref": "#/$defs/AgentIdentity" - } - } - } - }, - "A2AContext":{ - "type":"object", - "properties":{ - "from":{ - "oneOf": [ - { - "$ref":"#/$defs/A2AFullAgentContext" - }, - { - "$ref":"#/$defs/A2APartialAgentContext" - } - ] - }, - "to":{ - "oneOf": [ - { - "$ref":"#/$defs/A2APartialAgentContext" - }, - { - "$ref":"#/$defs/A2AFullAgentContext" - } - ] - } - } - }, - "A2AMessageSend": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "message/send", - "default": "message/send", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "payload": { - "additionalProperties": {}, - "title": "Payload", - "type": "object", - "description": "The raw json message of A2A protocol" - }, - "context": { - "$ref": "#/$defs/A2AContext" - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - }, - "required": [ - "payload", - "context" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "A2AMessageSend", - "type": "object" - } - }, - "A2AMessageStream": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "message/stream", - "default": "message/stream", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "payload": { - "additionalProperties": {}, - "title": "Payload", - "type": "object", - "description": "The raw json message of A2A protocol" - }, - "context": { - "$ref": "#/$defs/A2AContext" - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - }, - "required": [ - "payload", - "context" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "A2AMessageStream", - "type": "object" - } - }, - "A2ATaskPushNotificationConfigSet": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "task/pushNotificationConfig/set", - "default": "task/pushNotificationConfig/set", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "payload": { - "additionalProperties": {}, - "title": "Payload", - "type": "object", - "description": "The raw json message of A2A protocol" - }, - "context": { - "$ref": "#/$defs/A2AContext" - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - }, - "required": [ - "payload", - "context" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "A2ATaskPushNotificationConfigSet", - "type": "object" - } - }, - "A2ATaskPushNotificationConfigGet": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "task/pushNotificationConfig/get", - "default": "task/pushNotificationConfig/get", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "payload": { - "additionalProperties": {}, - "title": "Payload", - "type": "object", - "description": "The raw json message of A2A protocol" - }, - "context": { - "$ref": "#/$defs/A2AContext" - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - }, - "required": [ - "payload", - "context" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "A2ATaskPushNotificationConfigGet", - "type": "object" - } - }, - "A2ATasksResubscribe": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "tasks/resubscribe", - "default": "tasks/resubscribe", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "payload": { - "additionalProperties": {}, - "title": "Payload", - "type": "object", - "description": "The raw json message of A2A protocol" - }, - "context": { - "$ref": "#/$defs/A2AContext" - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - }, - "required": [ - "payload", - "context" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "A2ATasksResubscribe", - "type": "object" - } - }, - "A2ATasksCancel": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "tasks/cancel", - "default": "tasks/cancel", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "payload": { - "additionalProperties": {}, - "title": "Payload", - "type": "object", - "description": "The raw json message of A2A protocol" - }, - "context": { - "$ref": "#/$defs/A2AContext" - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - }, - "required": [ - "payload", - "context" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "A2ATasksCancel", - "type": "object" - } - }, - "A2ATaskGet": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "task/get", - "default": "task/get", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "payload": { - "additionalProperties": {}, - "title": "Payload", - "type": "object", - "description": "The raw json message of A2A protocol" - }, - "context": { - "$ref": "#/$defs/A2AContext" - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - }, - "required": [ - "payload", - "context" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "A2ATaskGet", - "type": "object" - } - }, - "Agent": { - "type": "object", - "title": "Agent", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string", - "title": "Name", - "description": "Name of the agent" - }, - "url": { - "type": "string", - "title": "URL", - "description": "A URL to the address the agent is hosted at." - }, - "description": { - "type": "string", - "title": "Description", - "description": "Description of the agent" - }, - "instructions": { - "type": "string", - "title": "Instructions", - "description": "Instructions for the agent" - }, - "tools": { - "type": "array", - "items": { - "$ref": "#/$defs/ToolDefinition" - } - }, - "mcpServers": { - "type": "array", - "items": { - "$ref": "#/$defs/MCPServer" - } - }, - "resources": { - "type": "array", - "items": { - "$ref": "#/$defs/Resource" - } - }, - "model": { - "$ref": "#/$defs/Model" - }, - "version": { - "title": "Version", - "type": "string" - }, - "provider": { - "$ref": "#/$defs/AgentProvider" - }, - "organization": { - "$ref": "#/$defs/Organization" - }, - "identity": { - "$ref": "#/$defs/AgentIdentity" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "id", - "name", - "url", - "instructions", - "version", - "provider", - "identity" - ] - }, - "AgentIdentity": { - "properties": { - "signatures": { - "type": "array", - "items": { - "$ref": "#/$defs/AgentSignature" - } - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "signatures" - ], - "type": "object", - "title": "AgentIdentity" - }, - "AgentProvider": { - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "url": { - "title": "Url", - "type": "string" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "name", - "url" - ], - "title": "AgentProvider", - "type": "object" - }, - "AgentSignature": { - "description": "AgentSignature represents a JWS signature of an Agent.\nThis follows the JSON format of an RFC 7515 JSON Web Signature (JWS).", - "properties": { - "header": { - "additionalProperties": {}, - "description": "The unprotected JWS header values.", - "type": "object" - }, - "protected": { - "description": "The protected JWS header for the signature. This is a Base64url-encoded\nJSON object, as per RFC 7515.", - "type": "string" - }, - "signature": { - "description": "The computed signature, Base64url-encoded.", - "type": "string" - } - }, - "required": [ - "header", - "protected", - "signature" - ], - "type": "object" - }, - "AgentTrigger": { - "type": "object", - "title": "AgentTrigger", - "properties": { - "type": { - "type": "string", - "enum": [ - "autonomous" - ], - "title": "Type" - }, - "content": { - "items": { - "$ref": "#/$defs/Part" - }, - "title": "Parts", - "type": "array" - }, - "event": { - "$ref": "#/$defs/AgentTriggerEvent" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "type", - "content", - "event" - ] - }, - "AgentTriggerEvent": { - "type": "object", - "title": "Event", - "description": "Event that triggered the agent", - "properties": { - "type": { - "type": "string", - "title": "Type", - "description": "Type of the event", - "examples": [ - "email", - "slackNotification", - "jiraTicket" - ] - }, - "id": { - "type": "string", - "title": "Id", - "description": "Id of the event" - } - }, - "required": [ - "id", - "type" - ] - }, - "AgentTriggerStep": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "steps/agentTrigger", - "default": "steps/agentTrigger", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "context": { - "$ref": "#/$defs/StepContext" - }, - "trigger": { - "$ref": "#/$defs/AgentTrigger" - } - }, - "required": [ - "context", - "trigger" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "AgentTriggerStep", - "type": "object", - "description": "Trigger of the agent. User message trigger should not go here, but should be included in the MessageStep." - }, - "ASOPRequest": { - "oneOf": [ - { - "$ref": "#/$defs/MessageStep" - }, - { - "$ref": "#/$defs/AgentTriggerStep" - }, - { - "$ref": "#/$defs/MemoryContextRetrievalStep" - }, - { - "$ref": "#/$defs/MemoryStoreStep" - }, - { - "$ref": "#/$defs/ToolCallRequestStep" - }, - { - "$ref": "#/$defs/ToolCallResultStep" - }, - { - "$ref": "#/$defs/KnowledgeRetrievalStep" - }, - { - "$ref": "#/$defs/PingRequest" - }, - { - "$ref": "#/$defs/A2AMessageSend" - }, - { - "$ref": "#/$defs/A2AMessageStream" - }, - { - "$ref": "#/$defs/A2ATaskPushNotificationConfigSet" - }, - { - "$ref": "#/$defs/A2ATaskPushNotificationConfigGet" - }, - { - "$ref": "#/$defs/A2ATasksResubscribe" - }, - { - "$ref": "#/$defs/A2ATasksCancel" - }, - { - "$ref": "#/$defs/A2ATaskGet" - }, - { - "$ref": "#/$defs/MCPMessage" - } - ], - "title": "ASOPRequest" - }, - "ASOPResponse": { - "oneOf": [ - { - "$ref": "#/$defs/ASOPSuccessResponse" - }, - { - "$ref": "#/$defs/PingRequestSuccessResponse" - }, - { - "$ref": "#/$defs/JSONRPCErrorResponse" - } - ] - }, - "ASOPSuccessResponse": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "result": { - "$ref": "#/$defs/ASOPSuccessResult" - } - }, - "required": [ - "id", - "result", - "jsonrpc" - ], - "type": "object", - "title": "ASOPSuccessResponse" - }, - "ASOPSuccessResult": { - "properties": { - "decision": { - "type": "string", - "title": "Decision", - "description": "The decision", - "enum": [ - "allow", - "deny", - "modify" - ] - }, - "reasoning": { - "type": "string", - "title": "Reasoning", - "description": "The reasoning of the decision" - }, - "reasonCode": { - "type": "array", - "items": { - "type": "string" - } - }, - "message": { - "type": "string", - "title": "Message", - "description": "Human readable message explaining the decision" - }, - "data": { - "type": "object", - "title": "Data", - "additionalProperties": {}, - "description": "Additional data" - }, - "modifiedRequest": { - "$ref": "#/$defs/ASOPRequest", - "description": "Modified request if the decision is modify." - } - }, - "type": "object", - "title": "Result", - "required": [ - "decision", - "message" - ] - }, - "DataPart": { - "properties": { - "kind": { - "const": "data", - "default": "data", - "description": "Type of the part", - "examples": [ - "data" - ], - "title": "Kind", - "type": "string" - }, - "data": { - "additionalProperties": {}, - "title": "Data", - "type": "object" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "data" - ], - "title": "DataPart", - "type": "object" - }, - "FilePart": { - "description": "Represents a File segment within parts.", - "properties": { - "file": { - "anyOf": [ - { - "$ref": "#/$defs/FileWithBytes" - }, - { - "$ref": "#/$defs/FileWithUri" - } - ], - "description": "File content either as url or bytes" - }, - "kind": { - "const": "file", - "description": "Part type - file for FileParts", - "type": "string" - }, - "metadata": { - "additionalProperties": {}, - "description": "Optional metadata associated with the part.", - "type": "object" - } - }, - "required": [ - "file", - "kind" - ], - "type": "object" - }, - "FileSource": { - "type": "object", - "title": "FileSource", - "properties": { - "kind": { - "type": "string", - "const": "file" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "url": { - "type": "string" - } - }, - "required": [ - "kind", - "id", - "name" - ] - }, - "FileWithBytes": { - "description": "Define the variant where 'bytes' is present and 'uri' is absent", - "properties": { - "bytes": { - "description": "base64 encoded content of the file", - "type": "string" - }, - "mimeType": { - "description": "Optional mimeType for the file", - "type": "string" - }, - "name": { - "description": "Optional name for the file", - "type": "string" - } - }, - "required": [ - "bytes" - ], - "type": "object" - }, - "FileWithUri": { - "description": "Define the variant where 'uri' is present and 'bytes' is absent", - "properties": { - "mimeType": { - "description": "Optional mimeType for the file", - "type": "string" - }, - "name": { - "description": "Optional name for the file", - "type": "string" - }, - "uri": { - "description": "URL for the File content", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - }, - "Identity": { - "oneOf": [ - { - "$ref": "#/$defs/User" - }, - { - "$ref": "#/$defs/MachineIdentity" - }, - { - "$ref": "#/$defs/AgentIdentity" - } - ], - "title": "Identity" - }, - "InternalError": { - "properties": { - "code": { - "const": -32603, - "default": -32603, - "description": "Error code", - "examples": [ - -32603 - ], - "title": "Code", - "type": "integer" - }, - "message": { - "const": "Internal error", - "default": "Internal error", - "description": "A short description of the error", - "examples": [ - "Internal error" - ], - "title": "Message", - "type": "string" - }, - "data": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Data" - } - }, - "required": [ - "code", - "message" - ], - "title": "InternalError", - "type": "object" - }, - "InvalidParamsError": { - "properties": { - "code": { - "const": -32602, - "default": -32602, - "description": "Error code", - "examples": [ - -32602 - ], - "title": "Code", - "type": "integer" - }, - "message": { - "const": "Invalid parameters", - "default": "Invalid parameters", - "description": "A short description of the error", - "examples": [ - "Invalid parameters" - ], - "title": "Message", - "type": "string" - }, - "data": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Data" - } - }, - "required": [ - "code", - "message" - ], - "title": "InvalidParamsError", - "type": "object" - }, - "InvalidRequestError": { - "properties": { - "code": { - "const": -32600, - "default": -32600, - "description": "Error code", - "examples": [ - -32600 - ], - "title": "Code", - "type": "integer" - }, - "message": { - "const": "Request payload validation error", - "default": "Request payload validation error", - "description": "A short description of the error", - "examples": [ - "Request payload validation error" - ], - "title": "Message", - "type": "string" - }, - "data": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Data" - } - }, - "required": [ - "code", - "message" - ], - "title": "InvalidRequestError", - "type": "object" - }, - "JSONParseError": { - "properties": { - "code": { - "const": -32700, - "default": -32700, - "description": "Error code", - "examples": [ - -32700 - ], - "title": "Code", - "type": "integer" - }, - "message": { - "const": "Invalid JSON payload", - "default": "Invalid JSON payload", - "description": "A short description of the error", - "examples": [ - "Invalid JSON payload" - ], - "title": "Message", - "type": "string" - }, - "data": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Data" - } - }, - "required": [ - "code", - "message" - ], - "title": "JSONParseError", - "type": "object" - }, - "JSONRPCError": { - "properties": { - "code": { - "title": "Code", - "type": "integer" - }, - "message": { - "title": "Message", - "type": "string" - }, - "data": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Data" - } - }, - "required": [ - "code", - "message" - ], - "title": "JSONRPCError", - "type": "object" - }, - "JSONRPCErrorResponse": { - "description": "Represents a JSON-RPC 2.0 Error Response object.", - "properties": { - "error": { - "anyOf": [ - { - "$ref": "#/$defs/JSONRPCError" - }, - { - "$ref": "#/$defs/JSONParseError" - }, - { - "$ref": "#/$defs/InvalidRequestError" - }, - { - "$ref": "#/$defs/MethodNotFoundError" - }, - { - "$ref": "#/$defs/InvalidParamsError" - }, - { - "$ref": "#/$defs/InternalError" - } - ] - }, - "id": { - "description": "An identifier identical to the correlated request", - "type": [ - "string", - "integer" - ] - }, - "jsonrpc": { - "const": "2.0", - "description": "Specifies the version of the JSON-RPC protocol. MUST be exactly \"2.0\".", - "type": "string" - } - }, - "required": [ - "error", - "id", - "jsonrpc" - ], - "type": "object" - }, - "JSONRPCRequest": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Id" - }, - "method": { - "title": "Method", - "type": "string" - }, - "params": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Params" - } - }, - "required": [ - "method" - ], - "title": "JSONRPCRequest", - "type": "object" - }, - "JSONRPCResponse": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Id" - }, - "result": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Result" - }, - "error": { - "anyOf": [ - { - "$ref": "#/$defs/JSONRPCError" - }, - { - "type": "null" - } - ], - "default": null - } - }, - "title": "JSONRPCResponse", - "type": "object" - }, - "KnowledgeRetrievalStep": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "steps/knowledgeRetrieval", - "default": "steps/knowledgeRetrieval", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "context": { - "$ref": "#/$defs/StepContext" - }, - "knowledgeStep": { - "$ref": "#/$defs/KnowledgeRetrievalStepParams" - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - } - }, - "required": [ - "context", - "knowledgeStep" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "KnowledgeRetrievalStep", - "type": "object" - }, - "KnowledgeRetrievalStepParams": { - "type": "object", - "properties": { - "query": { - "type": "string", - "title": "Query", - "description": "The query to retrieve knowledge" - }, - "keywords": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Keywords for knowledge retrieval hints" - }, - "results": { - "type": "array", - "items": { - "$ref": "#/$defs/KnowledgeRetrievalResult" - } - } - }, - "required": [ - "results" - ] - }, - "KnowledgeRetrievalResult": { - "type": "object", - "properties": { - "id": { - "type": "string", - "title": "Id" - }, - "content": { - "type": "string", - "title": "Content" - }, - "mimeType": { - "type": "string", - "title": "Mime Type", - "description": "The mime type of the content" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "id", - "content" - ] - }, - "MachineIdentity": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The unique identifier of the application or service" - }, - "name": { - "type": "string", - "description": "The name of the application or service" - }, - "organization": { - "$ref": "#/$defs/Organization", - "description": "The owning organization of the application or service" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "id" - ] - }, - "MCPMessage": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "protocols/MCP", - "default": "protocols/MCP", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "message": { - "additionalProperties": {}, - "title": "message", - "type": "object", - "description": "The raw json message of MCP protocol" - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - }, - "required": [ - "message" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "MCPMessage", - "type": "object", - "description": "Method that captures the MCP protocol messages." - } - }, - "MCPServer": { - "description": "Information about an MCP server.", - "properties": { - "name": { - "type": "string" - }, - "version": { - "type": "string" - }, - "url": { - "type": "string" - } - }, - "required": [ - "name", - "version" - ], - "type": "object" - }, - "MemoryContextRetrievalStep": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "steps/memoryContextRetrieval", - "default": "steps/memoryContextRetrieval", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "context": { - "$ref": "#/$defs/StepContext" - }, - "memory": { - "title": "memory", - "type": "array", - "items": { - "type": "string" - } - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - } - }, - "required": [ - "context", - "memory" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "MemoryContextRetrievalStep", - "type": "object" - }, - "MemoryStoreStep": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "steps/memoryStore", - "default": "steps/memoryStore", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "context": { - "$ref": "#/$defs/StepContext" - }, - "memory": { - "title": "memory", - "type": "array", - "items": { - "type": "string" - } - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - } - }, - "required": [ - "context", - "memory" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "MemoryStoreStep", - "type": "object" - }, - "LlmProvider": { - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "name" - ], - "title": "LlmProvider", - "type": "object" - }, - "Message": { - "properties": { - "role": { - "enum": [ - "user", - "agent", - "system" - ], - "title": "Role", - "type": "string" - }, - "content": { - "items": { - "$ref": "#/$defs/Part" - }, - "title": "Parts", - "type": "array" - }, - "id": { - "type": "string", - "title": "Id", - "description": "Id of the message" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "role", - "content", - "id" - ], - "title": "Message", - "type": "object" - }, - "MessageStep": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "steps/message", - "default": "steps/message", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "context": { - "$ref": "#/$defs/StepContext" - }, - "message": { - "$ref": "#/$defs/Message" - }, - "citations": { - "type": "array", - "items": { - "$ref": "#/$defs/Source" - } - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - } - }, - "required": [ - "context", - "message" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "MessageStep", - "type": "object" - }, - "MethodNotFoundError": { - "properties": { - "code": { - "const": -32601, - "default": -32601, - "description": "Error code", - "examples": [ - -32601 - ], - "title": "Code", - "type": "integer" - }, - "message": { - "const": "Method not found", - "default": "Method not found", - "description": "A short description of the error", - "examples": [ - "Method not found" - ], - "title": "Message", - "type": "string" - }, - "data": { - "const": null, - "default": null, - "title": "Data" - } - }, - "required": [ - "code", - "message", - "data" - ], - "title": "MethodNotFoundError", - "type": "object" - }, - "Organization": { - "properties": { - "id": { - "type": "string", - "description": "The unique identifier of the organization" - }, - "name": { - "type": "string", - "description": "The name of the organization" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "id" - ] - }, - "Model": { - "type": "object", - "title": "Model", - "properties": { - "id": { - "type": "string", - "description": "The unique identifier of the model" - }, - "name": { - "type": "string", - "description": "The name of the model" - }, - "provider": { - "$ref": "#/$defs/LlmProvider" - }, - "type": { - "type": "string", - "enum": [ - "chat", - "completion", - "embedding" - ] - }, - "maxTokens": { - "type": "integer" - }, - "defaultParams": { - "type": "object", - "additionalProperties": true - }, - "contextWindow": { - "type": "integer" - }, - "stopSequences": { - "type": "array", - "items": { - "type": "string" - } - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "id", - "name", - "provider" - ] - }, - "Part": { - "anyOf": [ - { - "$ref": "#/$defs/TextPart" - }, - { - "$ref": "#/$defs/FilePart" - }, - { - "$ref": "#/$defs/DataPart" - } - ], - "title": "Part" - }, - "PingRequest": { - "type": "object", - "title": "PingRequest", - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "ping", - "default": "ping", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "timestamp": { - "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp of the request" - }, - "timeout": { - "type": "integer", - "description": "Timeout in milliseconds", - "default": 5000 - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - } - } - }, - "required": [ - "method", - "id", - "timestamp" - ] - }, - "PingRequestSuccessResponse": { - "type": "object", - "title": "PingRequestSuccessResponse", - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "result": { - "$ref": "#/$defs/PingRequestResult" - } - }, - "required": [ - "id", - "result", - "jsonrpc" - ] - }, - "PingRequestResult": { - "type": "object", - "title": "PingRequestResult", - "properties": { - "status": { - "type": "string", - "enum": [ - "connected", - "error" - ] - }, - "version": { - "type": "string", - "description": "Agent version" - }, - "timestamp": { - "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp of the request" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "status", - "version", - "timestamp" - ] - }, - "Resource": { - "description": "A known resource that the server is capable of reading.", - "properties": { - "description": { - "description": "A description of what this resource represents.", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of this resource, if known.", - "type": "string" - }, - "name": { - "description": "A human-readable name for this resource.", - "type": "string" - }, - "id": { - "type": "string", - "description": "The unique identifier of the resource." - }, - "content": { - "type": "string", - "description": "The content of the resource." - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "id", - "name", - "content" - ], - "type": "object" - }, - "Session": { - "type": "object", - "title": "Session", - "properties": { - "id": { - "type": "string", - "title": "Id", - "description": "Id of the session / conversation" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "id" - ] - }, - "SiteSource": { - "type": "object", - "title": "SiteSource", - "properties": { - "kind": { - "const": "site", - "type": "string" - }, - "url": { - "type": "string" - } - }, - "required": [ - "kind", - "url" - ] - }, - "Source": { - "oneOf": [ - { - "$ref": "#/$defs/FileSource" - }, - { - "$ref": "#/$defs/SiteSource" - } - ], - "title": "Source" - }, - "StepContext": { - "type": "object", - "title": "StepContext", - "properties": { - "agent": { - "$ref": "#/$defs/Agent" - }, - "session": { - "$ref": "#/$defs/Session" - }, - "turnId": { - "type": "string", - "title": "TurnId", - "description": "Id of the current turn in the session" - }, - "stepId": { - "type": "string", - "title": "StepId", - "description": "Id of the current step in the session" - }, - "timestamp": { - "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp of the request" - }, - "identity": { - "$ref": "#/$defs/Identity" - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object" - }, - { - "type": "null" - } - ], - "default": null - }, - "required": [ - "agent", - "session", - "turnId", - "stepId", - "timestamp", - "identity" - ] - }, - "TextPart": { - "properties": { - "kind": { - "const": "text", - "default": "text", - "description": "Type of the part", - "examples": [ - "text" - ], - "title": "Kind", - "type": "string" - }, - "text": { - "title": "Text", - "type": "string" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "text" - ], - "title": "TextPart", - "type": "object" - }, - "ToolArgumentDefinition": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the argument", - "title": "Name" - }, - "id": { - "type": "string", - "description": "Unique identifier for the argument", - "title": "Id" - }, - "description": { - "type": "string", - "title": "Description", - "description": "Description of the argument" - }, - "type": { - "type": "string", - "title": "Type", - "description": "Type information for the argument", - "enum": [ - "string", - "number", - "boolean", - "object", - "array", - "null" - ] - }, - "mimeType": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "MIME type of the value if applicable", - "default": null, - "title": "Mimetype", - "examples": [ - "application/json", - "text/plain", - "image/png", - "audio/mpeg" - ] - }, - "required": { - "type": "boolean", - "title": "Required", - "description": "Whether the argument is required" - } - }, - "required": [ - "name", - "required" - ] - }, - "ToolArgumentValue": { - "type": "object", - "title": "ToolArgumentValue", - "properties": { - "name": { - "type": "string", - "title": "Name" - }, - "id": { - "type": "string", - "title": "Id" - }, - "value": { - "description": "The argument value", - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Value" - } - }, - "required": [ - "name", - "value" - ] - }, - "ToolCallRequestStep": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "steps/toolCallRequest", - "default": "steps/toolCallRequest", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "context": { - "$ref": "#/$defs/StepContext" - }, - "toolCallRequest": { - "$ref": "#/$defs/ToolCallRequest" - }, - "reasoning": { - "type": "string", - "title": "Reasoning" - } - }, - "required": [ - "context", - "toolCallRequest" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "ToolCallRequestStep", - "type": "object" - }, - "ToolCallResult": { - "type": "object", - "properties": { - "outputs": { - "type": "array", - "items": { - "$ref": "#/$defs/TextPart" - } - }, - "isError": { - "type": "boolean", - "description": "Whether the tool call resulted in an error" - } - }, - "required": [ - "outputs", - "isError" - ] - }, - "ToolCallResultStep": { - "properties": { - "jsonrpc": { - "const": "2.0", - "default": "2.0", - "title": "Jsonrpc", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Id" - }, - "method": { - "const": "steps/toolCallResult", - "default": "steps/toolCallResult", - "title": "Method", - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "context": { - "$ref": "#/$defs/StepContext" - }, - "toolCallResult": { - "type": "object", - "properties": { - "executionId": { - "type": "string", - "title": "ExecutionId", - "description": "Id of the execution set by the agent" - }, - "result": { - "$ref": "#/$defs/ToolCallResult" - } - }, - "required": [ - "executionId", - "result" - ] - } - }, - "required": [ - "context", - "toolCallResult" - ] - } - }, - "required": [ - "method", - "params", - "id" - ], - "title": "ToolCallResultStep", - "type": "object" - }, - "ToolDefinition": { - "type": "object", - "title": "ToolDefinition", - "properties": { - "name": { - "type": "string", - "title": "Name" - }, - "id": { - "type": "string", - "title": "Id" - }, - "description": { - "type": "string", - "title": "Description" - }, - "type": { - "type": "string", - "title": "Type" - }, - "arguments": { - "anyOf": [ - { - "type": "array", - "items": { - "$ref": "#/$defs/ToolArgumentDefinition" - } - }, - { - "type": "null" - } - ], - "title": "Arguments" - }, - "outputs": { - "anyOf": [ - { - "type": "array", - "items": { - "$ref": "#/$defs/ToolOutputDefinition" - } - }, - { - "type": "null" - } - ], - "title": "Outputs" - } - }, - "required": [ - "name", - "id", - "type", - "arguments", - "outputs" - ] - }, - "ToolCallRequest": { - "type": "object", - "title": "ToolCallRequest", - "properties": { - "executionId": { - "type": "string", - "title": "ExecutionId", - "description": "Id of the execution set by the agent" - }, - "toolId": { - "type": "string", - "title": "ToolId", - "description": "Id of the tool to be called" - }, - "inputs": { - "type": "array", - "items": { - "$ref": "#/$defs/ToolArgumentValue" - } - } - }, - "required": [ - "toolId", - "inputs", - "executionId" - ] - }, - "ToolOutputDefinition": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the output parameter", - "title": "Name" - }, - "id": { - "type": "string", - "description": "Unique identifier of the output parameter", - "title": "Id" - }, - "description": { - "type": "string", - "title": "Description", - "description": "Description of the tool output parameter" - }, - "type": { - "type": "string", - "title": "Type", - "description": "Type of the tool output parameter", - "enum": [ - "string", - "number", - "boolean", - "object", - "array", - "null" - ] - }, - "mimeType": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "MIME type of the value if applicable", - "default": null, - "title": "Mimetype", - "examples": [ - "application/json", - "text/plain", - "image/png", - "audio/mpeg" - ] - } - } - }, - "User": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The unique identifier of the user" - }, - "name": { - "type": "string", - "description": "The name of the user" - }, - "email": { - "type": "string", - "description": "The email of the user" - }, - "organization": { - "$ref": "#/$defs/Organization", - "description": "The organization of the user" - }, - "metadata": { - "anyOf": [ - { - "additionalProperties": {}, - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - } - }, - "required": [ - "id" - ] - } - } + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentcontrolstandard.org/schema/v0.1.0/acs_schema.json", + "title": "ACS Schema (v0.1.0 aggregator)", + "description": "Aggregator manifest for the modular ACS v0.1.0 JSON Schema package. Each subschema lives as its own file under specification/v0.1.0/ and carries an $id under https://acs.org/schema/v0.1.0/...; this file lists the package contents and cross-references them via relative $ref so a single document can be loaded as the entry point. Validators that need a self-contained bundle should resolve $ref by loading the referenced files; validators that follow $id can ignore this aggregator and load the modular files directly.", + "version": "0.1.0", + + "$defs": { + "RequestEnvelope": { "$ref": "../v0.1.0/request-envelope.json" }, + "ResponseEnvelope": { "$ref": "../v0.1.0/response-envelope.json" }, + "Handshake": { "$ref": "../v0.1.0/handshake.json" }, + "Provenance": { "$ref": "../v0.1.0/provenance.json" }, + "ProvenanceSummary": { "$ref": "../v0.1.0/provenance-summary.json" }, + "ContextEntry": { "$ref": "../v0.1.0/context-entry.json" }, + "AskDetails": { "$ref": "../v0.1.0/ask-details.json" }, + "DeferDetails": { "$ref": "../v0.1.0/defer-details.json" }, + "Modifications": { "$ref": "../v0.1.0/modifications.json" }, + + "Hook_SessionStart": { "$ref": "../v0.1.0/hooks/session-start.json" }, + "Hook_SessionEnd": { "$ref": "../v0.1.0/hooks/session-end.json" }, + "Hook_AgentTrigger": { "$ref": "../v0.1.0/hooks/agent-trigger.json" }, + "Hook_TurnStart": { "$ref": "../v0.1.0/hooks/turn-start.json" }, + "Hook_TurnEnd": { "$ref": "../v0.1.0/hooks/turn-end.json" }, + "Hook_UserMessage": { "$ref": "../v0.1.0/hooks/user-message.json" }, + "Hook_AgentResponse": { "$ref": "../v0.1.0/hooks/agent-response.json" }, + "Hook_KnowledgeRetrieval": { "$ref": "../v0.1.0/hooks/knowledge-retrieval.json" }, + "Hook_MemoryContextRetrieval": { "$ref": "../v0.1.0/hooks/memory-context-retrieval.json" }, + "Hook_MemoryStore": { "$ref": "../v0.1.0/hooks/memory-store.json" }, + "Hook_ToolCallRequest": { "$ref": "../v0.1.0/hooks/tool-call-request.json" }, + "Hook_ToolCallResult": { "$ref": "../v0.1.0/hooks/tool-call-result.json" }, + "Hook_PreCompact": { "$ref": "../v0.1.0/hooks/pre-compact.json" }, + "Hook_PostCompact": { "$ref": "../v0.1.0/hooks/post-compact.json" }, + "Hook_SubagentStart": { "$ref": "../v0.1.0/hooks/subagent-start.json" }, + "Hook_SubagentStop": { "$ref": "../v0.1.0/hooks/subagent-stop.json" }, + "Hook_SystemPing": { "$ref": "../v0.1.0/hooks/system-ping.json" }, + "Hook_AgbomSnapshot": { "$ref": "../v0.1.0/hooks/agbom-snapshot.json" }, + "Hook_AgbomChanged": { "$ref": "../v0.1.0/hooks/agbom-changed.json" }, + + "AgBOMComponent": { "$ref": "../v0.1.0/agbom/component.json" }, + "AgBOMDocument": { "$ref": "../v0.1.0/agbom/document.json" }, + + "InspectFormatMapping": { "$ref": "../v0.1.0/inspect/format-mapping.json" }, + "TraceOtelMapping": { "$ref": "../v0.1.0/trace/otel-mapping.json" }, + "TraceOcsfMapping": { "$ref": "../v0.1.0/trace/ocsf-mapping.json" } + }, + + "oneOf": [ + { "$ref": "#/$defs/RequestEnvelope" }, + { "$ref": "#/$defs/ResponseEnvelope" } + ] } diff --git a/specification/v0.1.0/agbom/component.json b/specification/v0.1.0/agbom/component.json new file mode 100644 index 0000000..886afba --- /dev/null +++ b/specification/v0.1.0/agbom/component.json @@ -0,0 +1,119 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/agbom/component.json", + "title": "ACS AgBOM Component", + "description": "Single component in an Agent Bill of Materials. Discriminated by 'type'. Every component carries provenance of its registration so AgBOM mutations are traceable in the same lineage system as data flow.", + "type": "object", + "required": ["id", "type"], + "properties": { + "id": { + "type": "string", + "description": "Stable identifier for this component within the AgBOM. SHOULD remain stable across version upgrades of the same logical component, so 'changed' diffs reference an unchanging id." + }, + "type": { + "type": "string", + "enum": ["model", "mcp_server", "a2a_peer", "tool", "knowledge_source", "memory_store", "agent_capability"], + "description": "Component type. Drives which type-specific fields are required." + }, + "name": { "type": "string" }, + "version": { "type": "string" }, + "provider": { "type": "string" }, + "endpoint": { + "type": "string", + "format": "uri", + "description": "URI per Section 3.11 (file:///, posix:///, https://, etc.)." + }, + "registration_provenance": { + "$ref": "../provenance.json", + "description": "Provenance of how this component entered the AgBOM. origin SHOULD be 'system' for framework/configuration registrations and 'user_input' / 'tool_output' for runtime-discovered or user-installed components." + }, + "model_fields": { + "type": "object", + "description": "Required when type='model'.", + "required": ["context_window"], + "properties": { + "context_window": { "type": "integer", "minimum": 0 }, + "args": { + "type": "object", + "additionalProperties": true, + "description": "Snapshot of model configuration (temperature, top_p, system prompt hash, etc.) at registration time." + } + } + }, + "mcp_server_fields": { + "type": "object", + "description": "Required when type='mcp_server'.", + "required": ["tools"], + "properties": { + "tools": { + "type": "array", + "items": { "type": "string" }, + "description": "Component ids of tool components exposed by this MCP server." + } + } + }, + "a2a_peer_fields": { + "type": "object", + "description": "Required when type='a2a_peer'.", + "required": ["protocol_version"], + "properties": { + "protocol_version": { "type": "string" }, + "agent_card_ref": { "type": "string", "format": "uri" } + } + }, + "tool_fields": { + "type": "object", + "description": "Required when type='tool'.", + "required": ["capability"], + "properties": { + "capability": { + "type": "string", + "description": "Abstract capability per Section 3.11 (e.g., filesystem.delete, network.egress, process.execute)." + } + } + }, + "knowledge_source_fields": { + "type": "object", + "description": "Required when type='knowledge_source'.", + "required": ["source_type"], + "properties": { + "source_type": { + "type": "string", + "enum": ["vector_db", "search_index", "knowledge_base", "web_search", "other"] + }, + "schema_ref": { "type": "string", "format": "uri" } + } + }, + "memory_store_fields": { + "type": "object", + "description": "Required when type='memory_store'.", + "required": ["scope", "store_type"], + "properties": { + "scope": { "type": "string", "enum": ["session", "user", "tenant", "global"] }, + "store_type": { "type": "string" }, + "path": { "type": "string", "format": "uri" }, + "window_size": { "type": "integer", "minimum": 0 } + } + }, + "agent_capability_fields": { + "type": "object", + "description": "Required when type='agent_capability'. References to other components in the same AgBOM by id.", + "required": ["description"], + "properties": { + "description": { "type": "string" }, + "tools": { "type": "array", "items": { "type": "string" } }, + "mcp_servers": { "type": "array", "items": { "type": "string" } }, + "a2a_peers": { "type": "array", "items": { "type": "string" } } + } + } + }, + "allOf": [ + { "if": { "properties": { "type": { "const": "model" } }, "required": ["type"] }, "then": { "required": ["name", "version", "provider", "endpoint", "model_fields"] } }, + { "if": { "properties": { "type": { "const": "mcp_server" } }, "required": ["type"] }, "then": { "required": ["name", "version", "endpoint", "mcp_server_fields"] } }, + { "if": { "properties": { "type": { "const": "a2a_peer" } }, "required": ["type"] }, "then": { "required": ["endpoint", "a2a_peer_fields"] } }, + { "if": { "properties": { "type": { "const": "tool" } }, "required": ["type"] }, "then": { "required": ["name", "version", "provider", "tool_fields"] } }, + { "if": { "properties": { "type": { "const": "knowledge_source" } }, "required": ["type"] }, "then": { "required": ["name", "knowledge_source_fields"] } }, + { "if": { "properties": { "type": { "const": "memory_store" } }, "required": ["type"] }, "then": { "required": ["name", "memory_store_fields"] } }, + { "if": { "properties": { "type": { "const": "agent_capability" } }, "required": ["type"] }, "then": { "required": ["name", "agent_capability_fields"] } } + ] +} diff --git a/specification/v0.1.0/agbom/document.json b/specification/v0.1.0/agbom/document.json new file mode 100644 index 0000000..2322cda --- /dev/null +++ b/specification/v0.1.0/agbom/document.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/agbom/document.json", + "title": "ACS AgBOM Document", + "description": "Canonical AgBOM document. Wire-shape for the Inspect pillar. Serializations to CycloneDX 1.6, SPDX 3.0, and SWID are deterministic derivations from this canonical form (see inspect/format-mapping.json). The Observed Agent always emits this canonical form on the wire; the Guardian renders serializations on request from downstream consumers.", + "type": "object", + "required": ["agbom_version", "agent", "components"], + "properties": { + "agbom_version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "Version of the AgBOM schema (independent of the ACS spec version, so AgBOM can evolve faster). v0.1 of this schema is '0.1.0'." + }, + "agent": { + "type": "object", + "required": ["agent_id"], + "properties": { + "agent_id": { "type": "string" }, + "name": { "type": "string" }, + "version": { "type": "string" }, + "vendor": { "type": "string" }, + "description": { "type": "string" } + } + }, + "components": { + "type": "array", + "items": { "$ref": "component.json" }, + "description": "Flat list of components. Inter-component references (an agent_capability listing its tools, an mcp_server listing its tools) are by component id, not by nesting, so the document remains diffable." + }, + "generated_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of AgBOM construction." + }, + "agbom_hash": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "Lowercase-hex SHA-256 of the RFC 8785 (JCS) canonicalization of this object with the agbom_hash field REMOVED. Lets Guardians and downstream consumers detect AgBOM mutations even when re-emitted snapshots are byte-identical to what they had before. RECOMMENDED." + } + } +} diff --git a/specification/v0.1.0/ask-details.json b/specification/v0.1.0/ask-details.json new file mode 100644 index 0000000..3cc2cea --- /dev/null +++ b/specification/v0.1.0/ask-details.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/ask-details.json", + "title": "ACS ASK Details", + "description": "Approver descriptor and prompt details. Required when decision is 'ask'.", + "type": "object", + "required": ["approver", "question", "timeout_seconds"], + "properties": { + "approver": { + "type": "object", + "required": ["type", "id"], + "properties": { + "type": { + "type": "string", + "enum": ["human", "agent", "service"] + }, + "id": { "type": "string" }, + "endpoint": { + "type": "string", + "format": "uri", + "description": "URI for agent or service approvers" + }, + "auth": { + "type": "object", + "description": "Authentication descriptor for non-human approvers", + "properties": { + "method": { "type": "string", "enum": ["bearer", "mtls"] } + } + } + } + }, + "question": { "type": "string" }, + "context": { "type": "string" }, + "options": { + "type": "array", + "items": { "type": "string" } + }, + "timeout_seconds": { "type": "integer", "minimum": 1 }, + "timeout_disposition": { + "type": "string", + "enum": ["allow", "deny"], + "default": "deny" + }, + "intent_extension": { + "type": "object", + "description": "Optional. Present when the ASK is being raised because the requested action is outside the current Intent.parsed and the approver's grant should extend the Intent. The approver's response (in approval-request semantics) carries the same shape; on grant, the Guardian appends these capabilities to Intent.parsed and writes a ContextEntry with step_type='intent_extension'. Required for IBAC dynamic operation: without it, ASK can only approve a single decision and Intent cannot grow within a session, breaking the IBAC escalation flow.", + "required": ["capabilities", "scope"], + "properties": { + "capabilities": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "tool": { "type": "string" }, + "operation": { "type": "string" }, + "resource": { "type": "string" } + }, + "description": "Capability tuples to add to Intent.parsed. Same shape as Intent.parsed entries in agent-trigger.json." + } + }, + "scope": { + "type": "string", + "enum": ["this_request", "session"], + "description": "'this_request' = capabilities apply only to the in-flight request, not added to Intent.parsed. 'session' = capabilities are added to Intent.parsed for the remainder of the session." + }, + "provenance": { + "$ref": "provenance.json", + "description": "Provenance attached to the extension. origin SHOULD identify the approver (typically 'user_input' for human approvers, 'agent_generated' for agent approvers, 'external' for service approvers). source_id SHOULD identify the specific approver. Lets auditors distinguish parser-derived capabilities from approver-extended ones." + } + } + } + } +} diff --git a/specification/v0.1.0/context-entry.json b/specification/v0.1.0/context-entry.json new file mode 100644 index 0000000..ebee864 --- /dev/null +++ b/specification/v0.1.0/context-entry.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/context-entry.json", + "title": "ACS ContextEntry", + "description": "Append-only entry in a SessionContext's audit chain. Maintained server-side by the Guardian; not transmitted in full on the wire (the wire-visible commitment is chain_hash inside the request envelope's metadata.session_state). Defined here so that hash-chain verification is portable across implementations: any conformant Guardian MUST produce the same entry_hash for the same step content under the rules below, otherwise cross-implementation audit comparison and chain-mismatch detection break. Storage representation is implementation-defined (in-memory, append-only log, SQL row, etc.); this schema constrains the canonical form used for hashing and audit export, not how Guardians store the data internally.", + "type": "object", + "required": ["entry_id", "step_id", "step_type", "entry_hash"], + "properties": { + "entry_id": { + "type": "string", + "description": "Identifier for this entry, unique within the session." + }, + "step_id": { + "type": "string", + "description": "Identifier of the step that produced this entry. SHOULD equal the request_id of the originating envelope." + }, + "step_type": { + "type": "string", + "description": "The method name from the originating request envelope (e.g., 'steps/toolCallRequest', 'steps/toolCallResult')." + }, + "request_hash": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "Lowercase-hex SHA-256 of the JCS-canonicalized (RFC 8785) request envelope's params object. RECOMMENDED. Without request_hash, the chain commits only to step metadata (step_id, step_type) and not to the actual request content; retroactive request-content tampering would be undetectable from the chain alone. RECOMMENDED for any deployment that relies on the chain for tamper-evidence." + }, + "provenance_summary": { + "$ref": "provenance-summary.json", + "description": "Optional condensed view of provenance facts at this step. Included in the entry's content (and therefore committed to by entry_hash) when present." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when the entry was written. RECOMMENDED for audit reconstruction. Included in the entry's content when present." + }, + "previous_hash": { + "type": ["string", "null"], + "pattern": "^[0-9a-f]{64}$", + "description": "Lowercase-hex SHA-256 entry_hash of the previous ContextEntry in the chain. NULL (or absent) for the first entry in a session. MUST be present and non-null for every entry except the first." + }, + "entry_hash": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "Lowercase-hex SHA-256 commitment to this entry plus the prior chain. Computed deterministically as SHA-256(content_bytes || prev_hash_bytes), where: (1) content_bytes is the UTF-8 encoding of the RFC 8785 (JCS) canonicalization of this object with the entry_hash and previous_hash fields REMOVED; (2) prev_hash_bytes is the raw 32-byte decoding of previous_hash, or the empty byte string if previous_hash is null/absent (first entry); (3) || denotes byte concatenation. Conformant Guardians MUST follow this rule exactly for chains to be cross-implementation verifiable. Alternative canonicalization schemes are NOT permitted within v0.1; future versions MAY negotiate alternatives via the handshake." + } + } +} diff --git a/specification/v0.1.0/defer-details.json b/specification/v0.1.0/defer-details.json new file mode 100644 index 0000000..a2026dc --- /dev/null +++ b/specification/v0.1.0/defer-details.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/defer-details.json", + "title": "ACS DEFER Details", + "description": "Deferral metadata when the Guardian could not reach a verdict. Required when decision is 'defer'.", + "type": "object", + "required": ["reason", "resolution_method", "resolution_timeout_ms"], + "properties": { + "reason": { + "type": "string", + "enum": [ + "insufficient_context", + "conflicting_policies", + "low_confidence", + "pending_dependency" + ] + }, + "resolution_method": { + "type": "string", + "enum": ["additional_context", "human_approval", "timeout"] + }, + "resolution_timeout_ms": { "type": "integer", "minimum": 0 }, + "timeout_decision": { + "type": "string", + "enum": ["deny", "ask"], + "default": "deny" + }, + "required_context": { + "type": "array", + "items": { "type": "string" }, + "description": "Names of inputs whose presence would resolve the deferral" + } + } +} diff --git a/specification/v0.1.0/handshake.json b/specification/v0.1.0/handshake.json new file mode 100644 index 0000000..15ba8dd --- /dev/null +++ b/specification/v0.1.0/handshake.json @@ -0,0 +1,158 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/handshake.json", + "title": "ACS Handshake", + "description": "Capability negotiation messages exchanged at session start, before any hook traffic. The Observed Agent sends a ClientHello; the Guardian Agent responds with a ServerHello. Version mismatch terminates with an UNSUPPORTED_VERSION error in the JSON-RPC error envelope. Unknown fields MUST be ignored at every level. This file defines both message shapes via $defs; the wire encoding wraps them in the standard request/response envelope using method 'handshake/hello'.", + "type": "object", + "$defs": { + "ClientHello": { + "type": "object", + "required": [ + "acs_versions_supported", + "methods_implemented", + "transports_supported", + "provenance_producer" + ], + "properties": { + "acs_versions_supported": { + "type": "array", + "items": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" }, + "minItems": 1, + "description": "Spec versions the Observed Agent can speak (semver). Guardian selects one in ServerHello.negotiated_version." + }, + "methods_implemented": { + "type": "array", + "items": { + "type": "string", + "pattern": "^(steps/|protocols/|agbom/|trace/|system/|handshake/|wrapped:).+" + }, + "description": "Hook and wrapped-protocol method names the Observed Agent will emit during this session. Reserved namespaces: steps/* (Instrument hooks), protocols/* (wrapped MCP/A2A), agbom/* (Inspect AgBOM methods — required when ACS-Inspect profile is claimed; agbom/changed additionally required under ACS-Inspect-Dynamic), handshake/* (capability negotiation), trace/* (reserved for future ACS-native trace methods), system/* (low-level transport/control; system/ping for liveness — clients SHOULD include it), wrapped: (explicit-version wrapped form). Used by the Guardian to know which hooks to expect and which it can safely treat as not-emitted." + }, + "transports_supported": { + "type": "array", + "items": { "type": "string", "enum": ["http", "https", "stdio"] }, + "minItems": 1 + }, + "max_payload_size_bytes": { + "type": "integer", + "minimum": 1, + "description": "Maximum payload size the Observed Agent is willing to send in a single request." + }, + "provenance_producer": { + "type": "string", + "enum": ["framework", "llm", "none"], + "description": "Declares who populates Provenance objects. 'framework' = deterministic framework code (REQUIRED for IFC paradigms). 'llm' = the LLM produces them (NOT RECOMMENDED — Guardians SHOULD reject this and refuse the session if their policy requires Provenance). 'none' = no Provenance is emitted; Guardians whose policies require Provenance MUST refuse the session at handshake time rather than silently degrading enforcement." + }, + "wrapped_protocols": { + "type": "array", + "items": { + "type": "object", + "required": ["protocol", "version"], + "properties": { + "protocol": { "type": "string", "enum": ["MCP", "A2A"] }, + "version": { "type": "string", "description": "Version of the wrapped protocol (e.g., '2025-06-18')." } + } + }, + "description": "Wrapped sub-protocols this Observed Agent will tunnel over ACS. Each entry pins a specific version so the Guardian can validate wrapped payloads. In v0.1, only MCP wrapping is specified (protocols/MCP/*); A2A is reserved for v0.2 and MAY be advertised for forward-compatibility but has no normative wrapping semantics in v0.1." + }, + "profiles_supported": { + "type": "array", + "items": { + "type": "string", + "enum": ["acs-core", "acs-trace", "acs-inspect", "acs-inspect-dynamic", "acs-provenance", "acs-crypto", "acs-audit"] + }, + "description": "Conformance profiles this Observed Agent implements. 'acs-core' is the mandatory baseline and SHOULD always be included. Other profiles are optional and independently claimable. See Section 4 (Conformance Profiles) for definitions." + } + } + }, + "ServerHello": { + "type": "object", + "required": ["negotiated_version", "methods_evaluated", "selected_transport", "timeout_config"], + "properties": { + "negotiated_version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "Version selected by the Guardian from the client's acs_versions_supported. MUST match the client's major version." + }, + "methods_evaluated": { + "type": "array", + "items": { "type": "string" }, + "description": "Subset of the client's methods_implemented that this Guardian will actually evaluate. Methods listed by the client but absent here are NOT evaluated; the Guardian's enforcement does not cover them. Clients MAY still emit them for audit but MUST treat them as ALLOW-by-default." + }, + "selected_transport": { + "type": "string", + "enum": ["http", "https", "stdio"] + }, + "signature_algorithms_supported": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "HMAC-SHA256", + "ECDSA-P256", + "RSA-PSS-SHA256", + "ML-DSA-65", + "ML-DSA-44", + "ML-DSA-87", + "SLH-DSA-128s", + "SLH-DSA-128f", + "ML-DSA-65+ECDSA-P256", + "ML-DSA-65+RSA-PSS-SHA256" + ] + }, + "description": "Signature algorithms the Guardian can verify on incoming requests. Empty array = signatures not supported / not required. 'HMAC-SHA256' is the v0.1 RECOMMENDED default (simplest deployment path). Classical asymmetric ('ECDSA-P256', 'RSA-PSS-SHA256') and PQC ('ML-DSA-*', 'SLH-DSA-*') are OPTIONAL. A future ACS version is expected to promote PQC to RECOMMENDED once ecosystem support matures." + }, + "timeout_config": { + "type": "object", + "required": ["default_ms"], + "properties": { + "default_ms": { + "type": "integer", + "minimum": 1, + "description": "Default timeout for any method not listed in per_method_ms." + }, + "per_method_ms": { + "type": "object", + "additionalProperties": { "type": "integer", "minimum": 1 }, + "description": "Per-method timeout overrides. Keys are method names (e.g., 'steps/toolCallRequest')." + } + } + }, + "approver_types_supported": { + "type": "array", + "items": { "type": "string", "enum": ["human", "agent", "service"] }, + "description": "Approver types this Guardian can route ASK decisions to." + }, + "policy_requires_provenance": { + "type": "boolean", + "description": "If true, this Guardian's policy requires Provenance on data-bearing fields; clients with provenance_producer='none' MUST be rejected before session starts." + }, + "agbom_serializations_supported": { + "type": "array", + "items": { + "type": "string", + "enum": ["canonical", "cyclonedx-1.6", "spdx-3.0", "swid"] + }, + "description": "Inspect-pillar serializations this Guardian can render the canonical AgBOM into on request. Deployments claiming ACS-Inspect MUST include at least one of {cyclonedx-1.6, spdx-3.0, swid} in addition to 'canonical' (the wire-shape). Guardians not claiming ACS-Inspect MAY omit this field. The Observed Agent always emits the canonical form on the wire; serializations are derivations the Guardian produces for downstream consumers." + }, + "trace_emission": { + "type": "object", + "description": "Trace-pillar negotiation. Deployments claiming ACS-Trace MUST emit Trace events under at least one of OTel or OCSF for every supported ACS step. Deployments not claiming ACS-Trace SHOULD still emit Trace events where feasible.", + "properties": { + "otel_enabled": { "type": "boolean", "description": "Guardian (or its co-deployed Trace sink) emits OTel spans per the Section 3.15 mapping." }, + "ocsf_enabled": { "type": "boolean", "description": "Guardian (or its co-deployed Trace sink) emits OCSF events per the Section 3.15 mapping." }, + "otel_collector_endpoint": { "type": "string", "format": "uri", "description": "Optional OTLP endpoint the Observed Agent MAY send spans to directly (when the deployment splits trace emission across both sides)." } + } + }, + "profiles_accepted": { + "type": "array", + "items": { + "type": "string", + "enum": ["acs-core", "acs-trace", "acs-inspect", "acs-inspect-dynamic", "acs-provenance", "acs-crypto", "acs-audit"] + }, + "description": "Conformance profiles accepted for this session. The Guardian selects from the client's profiles_supported based on its own capabilities and policy requirements. A Guardian MAY refuse a session if the client does not declare a profile the Guardian's policy requires." + } + } + } + } +} diff --git a/specification/v0.1.0/hooks/agbom-changed.json b/specification/v0.1.0/hooks/agbom-changed.json new file mode 100644 index 0000000..38ae39f --- /dev/null +++ b/specification/v0.1.0/hooks/agbom-changed.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/agbom-changed.json", + "title": "agbom/changed payload", + "description": "AgBOM mutation notification. Emitted by the Observed Agent whenever a component is added, removed, or version-changed mid-session. MAY be denied by the Guardian to block a hot-swap (e.g., refuse a model upgrade mid-session); otherwise audited. MUST be written into the SessionContext audit chain alongside the originating mutation.", + "type": "object", + "oneOf": [ + { "required": ["diff"] }, + { "required": ["full_snapshot"] } + ], + "properties": { + "diff": { + "type": "object", + "description": "Incremental change set. Preferred for small mutations; the resulting AgBOM after applying the diff MUST equal the AgBOM the Observed Agent would emit as a fresh snapshot.", + "minProperties": 1, + "anyOf": [ + { "required": ["added"] }, + { "required": ["removed"] }, + { "required": ["changed"] } + ], + "properties": { + "added": { + "type": "array", + "items": { "$ref": "../agbom/component.json" } + }, + "removed": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "type"], + "properties": { + "id": { "type": "string" }, + "type": { "type": "string" } + }, + "description": "Component identifier and type for removal — full component record not needed." + } + }, + "changed": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "type", "before", "after"], + "properties": { + "id": { "type": "string" }, + "type": { "type": "string" }, + "before": { "$ref": "../agbom/component.json" }, + "after": { "$ref": "../agbom/component.json" } + } + } + } + } + }, + "full_snapshot": { + "$ref": "../agbom/document.json", + "description": "Full re-snapshot. Used when the change is large or when the Observed Agent cannot construct a minimal diff." + }, + "reason": { + "type": "string", + "enum": ["component_added", "component_removed", "component_upgraded", "configuration_changed", "discovery", "user_action", "policy_action"], + "description": "Why the AgBOM changed. Lets policies key on cause (e.g., deny user-initiated tool installs but allow framework-driven upgrades)." + }, + "trigger_step_id": { + "type": "string", + "description": "Optional step_id of the originating action that caused the AgBOM mutation (e.g., the toolCallRequest that installed a new MCP server)." + } + } +} diff --git a/specification/v0.1.0/hooks/agbom-snapshot.json b/specification/v0.1.0/hooks/agbom-snapshot.json new file mode 100644 index 0000000..2629426 --- /dev/null +++ b/specification/v0.1.0/hooks/agbom-snapshot.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/agbom-snapshot.json", + "title": "agbom/snapshot payload", + "description": "Full Agent Bill of Materials snapshot. Emitted by the Observed Agent once per session, after sessionStart and before any content-bearing hook fires; also re-emitted after any handshake renegotiation. Disposition is normally 'allow'; Guardians MAY return 'deny' to refuse a session whose component graph contains a banned model, tool, peer, or knowledge source. agbom/snapshot MUST be written into the SessionContext audit chain (the AgBOM is part of the session's security-relevant history).", + "type": "object", + "required": ["agbom"], + "properties": { + "agbom": { + "$ref": "../agbom/document.json", + "description": "The canonical AgBOM document. Serializations (CycloneDX, SPDX, SWID) are derivations the Guardian produces for downstream consumers." + }, + "trigger": { + "type": "string", + "enum": ["session_start", "renegotiation", "policy_request"], + "description": "Why this snapshot is being emitted. 'session_start' is the normal first-snapshot case; 'renegotiation' follows handshake renegotiation; 'policy_request' is a Guardian-initiated re-snapshot if the spec ever adds Guardian-to-Observed query (v0.2)." + } + } +} diff --git a/specification/v0.1.0/hooks/agent-response.json b/specification/v0.1.0/hooks/agent-response.json new file mode 100644 index 0000000..63ec4d3 --- /dev/null +++ b/specification/v0.1.0/hooks/agent-response.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/agent-response.json", + "title": "steps/agentResponse payload", + "description": "Payload for the agentResponse hook. Fires when the agent's response is ready, before reaching the user.", + "type": "object", + "required": ["content"], + "properties": { + "content": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["type", "value", "provenance"], + "properties": { + "type": { "type": "string", "enum": ["text", "image", "audio", "file"] }, + "value": {}, + "provenance": { "$ref": "../provenance.json" } + } + } + }, + "reasoning": { + "type": "string", + "description": "Optional natural-language rationale produced by the agent" + } + } +} diff --git a/specification/v0.1.0/hooks/agent-trigger.json b/specification/v0.1.0/hooks/agent-trigger.json new file mode 100644 index 0000000..dbcc2ed --- /dev/null +++ b/specification/v0.1.0/hooks/agent-trigger.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/agent-trigger.json", + "title": "steps/agentTrigger payload", + "description": "Payload for the agentTrigger hook. Fires when an agent is activated.", + "type": "object", + "required": ["trigger_type"], + "properties": { + "trigger_type": { + "type": "string", + "enum": ["user_message", "scheduled", "external_event", "a2a_inbound", "system"] + }, + "trigger_source": { + "type": "object", + "description": "Source descriptor; shape depends on trigger_type" + }, + "intent": { + "type": "object", + "description": "Optional Intent registered at session start (IBAC adopters populate)", + "properties": { + "raw": { "type": "string" }, + "parsed": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tool": { "type": "string" }, + "operation": { "type": "string" }, + "resource": { "type": "string" } + } + } + }, + "parser_provenance": { "$ref": "../provenance.json" }, + "scope_mode": { + "type": "string", + "enum": ["strict", "moderate", "permissive"] + } + }, + "if": { "required": ["parsed"] }, + "then": { "required": ["parser_provenance"] } + } + } +} diff --git a/specification/v0.1.0/hooks/knowledge-retrieval.json b/specification/v0.1.0/hooks/knowledge-retrieval.json new file mode 100644 index 0000000..5f21120 --- /dev/null +++ b/specification/v0.1.0/hooks/knowledge-retrieval.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/knowledge-retrieval.json", + "title": "steps/knowledgeRetrieval payload", + "description": "Payload for the knowledgeRetrieval hook. Fires when the agent retrieves knowledge (RAG, vector DB, etc.).", + "type": "object", + "required": ["query", "results"], + "properties": { + "query": { + "type": "object", + "required": ["value", "provenance"], + "properties": { + "value": { "type": "string" }, + "provenance": { "$ref": "../provenance.json" } + } + }, + "source": { + "type": "object", + "description": "Knowledge source descriptor", + "properties": { + "name": { "type": "string" }, + "type": { "type": "string", "enum": ["vector_db", "search_index", "knowledge_base", "web_search", "other"] }, + "version": { "type": "string" } + } + }, + "results": { + "type": "array", + "items": { + "type": "object", + "required": ["value", "provenance"], + "properties": { + "value": {}, + "source_id": { "type": "string" }, + "score": { "type": "number" }, + "provenance": { "$ref": "../provenance.json" } + } + } + } + } +} diff --git a/specification/v0.1.0/hooks/memory-context-retrieval.json b/specification/v0.1.0/hooks/memory-context-retrieval.json new file mode 100644 index 0000000..050b816 --- /dev/null +++ b/specification/v0.1.0/hooks/memory-context-retrieval.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/memory-context-retrieval.json", + "title": "steps/memoryContextRetrieval payload", + "description": "Payload for the memoryContextRetrieval hook. Fires when the agent reads from persistent memory.", + "type": "object", + "required": ["results"], + "properties": { + "query": { + "type": "object", + "properties": { + "value": { "type": "string" }, + "provenance": { "$ref": "../provenance.json" } + } + }, + "memory_store": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "scope": { "type": "string", "enum": ["session", "user", "tenant", "global"] } + } + }, + "results": { + "type": "array", + "items": { + "type": "object", + "required": ["value", "provenance"], + "properties": { + "memory_id": { "type": "string" }, + "value": {}, + "stored_at": { "type": "string", "format": "date-time" }, + "provenance": { "$ref": "../provenance.json" } + } + } + } + } +} diff --git a/specification/v0.1.0/hooks/memory-store.json b/specification/v0.1.0/hooks/memory-store.json new file mode 100644 index 0000000..6df001c --- /dev/null +++ b/specification/v0.1.0/hooks/memory-store.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/memory-store.json", + "title": "steps/memoryStore payload", + "description": "Payload for the memoryStore hook. Fires before the agent writes to persistent memory.", + "type": "object", + "required": ["content"], + "properties": { + "memory_id": { + "type": "string", + "description": "Identifier for the memory entry being written or updated" + }, + "memory_store": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "scope": { "type": "string", "enum": ["session", "user", "tenant", "global"] } + } + }, + "operation": { + "type": "string", + "enum": ["create", "update", "delete"] + }, + "content": { + "type": "object", + "required": ["value", "provenance"], + "properties": { + "value": {}, + "provenance": { "$ref": "../provenance.json" } + } + }, + "metadata": { + "type": "object", + "additionalProperties": true + } + } +} diff --git a/specification/v0.1.0/hooks/post-compact.json b/specification/v0.1.0/hooks/post-compact.json new file mode 100644 index 0000000..fc37d58 --- /dev/null +++ b/specification/v0.1.0/hooks/post-compact.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/post-compact.json", + "title": "steps/postCompact payload", + "description": "Fires after compaction has occurred. The hook is audit + provenance-binding: the Guardian records the new summary's content and lineage, and MAY return 'modify' to rewrite or further redact the summary, but MUST NOT return 'deny' — compaction has already happened and the audit chain MUST record the post-compact state. Provenance on the summary is normative on origin and derived_from: origin MUST be 'agent_generated', and derived_from MUST equal the union of provenance_ids of every entry in entries_compacted. The framework — not the LLM — populates derived_from. The Guardian projects this lineage onto its own trust classification in policy (v0.1 does not carry a trust field on the wire — see §3.7); the integrity property the Guardian MUST enforce is the standard monotonicity rule, namely that the summary's effective trust cannot exceed the minimum effective trust of its derived_from entries. No amount of LLM processing launders untrusted-classified data into trusted-classified data.", + "type": "object", + "required": ["summary", "entries_compacted", "pre_compact_chain_hash", "post_compact_chain_hash"], + "properties": { + "summary": { + "type": "object", + "required": ["value", "provenance"], + "properties": { + "value": { + "type": "string", + "description": "The post-compaction summary text." + }, + "provenance": { + "$ref": "../provenance.json", + "description": "MUST have origin='agent_generated' and derived_from = union of provenance_ids of every entry in entries_compacted. Conformant frameworks MUST compute this deterministically; the LLM MUST NOT be given the opportunity to forge or relabel it. Trust classification is computed by the Guardian against local policy and is not carried in this v0.1 wire object (see §3.7); the Guardian MUST enforce the monotonicity property that the summary's effective trust does not exceed the minimum effective trust of its derived_from lineage." + } + } + }, + "entries_compacted": { + "type": "array", + "minItems": 1, + "items": { "type": "string" }, + "description": "The step_ids that were summarized. SHOULD equal the entries_to_compact from the matching steps/preCompact (modulo a Guardian-modified set if the preCompact returned 'modify' to scope the compaction)." + }, + "pre_compact_chain_hash": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "The chain_hash at the moment compaction began (i.e., the chain_hash of the most recent ContextEntry written before this hook). Required for chain-replay verification: a verifier can check that the entries between this hash and post_compact_chain_hash correspond to the entries_compacted list." + }, + "post_compact_chain_hash": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "The chain_hash after this postCompact ContextEntry is written. The next per-step ContextEntry will have previous_hash equal to this value." + }, + "lineage_depth_after": { + "type": "integer", + "minimum": 0, + "description": "Optional. The summary's max lineage depth, computed as max(depth) + 1 over the depths of entries in derived_from. Lets policies bound 'how many compactions deep is this summary' without recomputing the chain." + } + } +} diff --git a/specification/v0.1.0/hooks/pre-compact.json b/specification/v0.1.0/hooks/pre-compact.json new file mode 100644 index 0000000..19c5eea --- /dev/null +++ b/specification/v0.1.0/hooks/pre-compact.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/pre-compact.json", + "title": "steps/preCompact payload", + "description": "Fires before the runtime LLM compresses its context window into a summary. Decision-eligible — Guardians MAY return 'deny' to block compaction (e.g., because deployment policy forbids compacting after data the Guardian classifies as untrusted has entered until a re-grounding from a policy-trusted origin occurs). Compaction is the chokepoint where provenance can be laundered: the summary that comes out is new agent_generated content whose lineage spans every entry in the pre-compaction context. Without this hook, AARM cumulative-context tracking breaks across compaction, FIDES's monotonicity claim is unverifiable, and 'don't compact across a trust boundary' policies have nowhere to attach. (Trust classification is computed by the Guardian against local policy in v0.1 — see §3.7.)", + "type": "object", + "required": ["entries_to_compact", "triggered_by"], + "properties": { + "entries_to_compact": { + "type": "array", + "minItems": 1, + "items": { "type": "string" }, + "description": "Array of step_ids that the framework intends to summarize. Lets the Guardian see exactly which prior steps will be folded into the summary, which is what its provenance lineage will reference after compaction." + }, + "pre_compact_provenance_summary": { + "$ref": "../provenance-summary.json", + "description": "Optional condensed view of the provenance facts in the to-be-compacted region (origins observed, entry counts, max lineage depth, earliest step per origin, etc.). RECOMMENDED — without it the Guardian must derive these facts itself by walking the listed entries. Lets a policy decide quickly whether the compaction is safe; the Guardian projects the origin facts onto its own trust classification in policy." + }, + "triggered_by": { + "type": "string", + "enum": ["size_threshold", "manual", "agent_initiated", "framework_initiated"], + "description": "What caused the framework to compact now. 'size_threshold' = the runtime hit a configured token/step limit. 'manual' = the user requested it. 'agent_initiated' = the runtime LLM emitted a compaction tool call (NOT RECOMMENDED but observed in some agents). 'framework_initiated' = scheduled or policy-driven by the framework itself." + }, + "estimated_input_tokens": { + "type": "integer", + "minimum": 0, + "description": "Optional. Rough token count of the region about to be compacted. Useful for policies that key on compaction magnitude." + }, + "target_summary_tokens": { + "type": "integer", + "minimum": 0, + "description": "Optional. Target size of the resulting summary. Lets the Guardian reason about how aggressive the compression is." + } + } +} diff --git a/specification/v0.1.0/hooks/session-end.json b/specification/v0.1.0/hooks/session-end.json new file mode 100644 index 0000000..a76c302 --- /dev/null +++ b/specification/v0.1.0/hooks/session-end.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/session-end.json", + "title": "steps/sessionEnd payload", + "description": "Payload for the sessionEnd hook. Fires when the session terminates. Asynchronous; does not block agent.", + "type": "object", + "required": ["reason"], + "properties": { + "reason": { + "type": "string", + "enum": ["completed", "cancelled", "error", "timeout", "abandoned"] + }, + "summary": { + "type": "object", + "description": "Optional summary written by the agent's framework", + "properties": { + "outcome": { "type": "string" }, + "step_count": { "type": "integer", "minimum": 0 }, + "errors": { + "type": "array", + "items": { "type": "string" } + } + } + }, + "final_chain_hash": { + "type": "string", + "description": "Final SessionContext chain hash from the agent's perspective. Guardian SHOULD verify against its own chain." + } + } +} diff --git a/specification/v0.1.0/hooks/session-start.json b/specification/v0.1.0/hooks/session-start.json new file mode 100644 index 0000000..18a595d --- /dev/null +++ b/specification/v0.1.0/hooks/session-start.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/session-start.json", + "title": "steps/sessionStart payload", + "description": "Fires once at session initiation, before any other steps/* hook for the session_id. Establishes the session boundary, opens the receipt chain (the first ContextEntry has previous_hash=null), and lets Guardians attach session-level state (policy bundle selection, tenant routing, identity binding) before content-bearing hooks fire. All payload fields are OPTIONAL — the simplest valid invocation is an empty payload that just marks the session boundary. Required by paradigms that need an explicit session boundary marker (AARM cumulative-context, IBAC intent lock at session start) and recommended for any deployment that wants to attach session-level identity or policy state out-of-band from agentTrigger.", + "type": "object", + "properties": { + "user_identity": { + "type": "object", + "description": "Authenticated principal initiating the session. Shape is platform-defined; typical fields shown.", + "properties": { + "user_id": { "type": "string" }, + "roles": { "type": "array", "items": { "type": "string" } }, + "authentication_method": { "type": "string" } + } + }, + "policy_mode": { + "type": "string", + "enum": ["strict", "moderate", "permissive"], + "description": "Declared scope_mode for this session. If Intent is also established here (or later via agentTrigger), Intent.scope_mode MUST equal this value." + }, + "intent": { + "type": "object", + "description": "Optional Intent established at session start. Same shape as Intent inside agentTrigger. When present, the Guardian MUST treat this as the canonical session Intent: any later agentTrigger that attempts to establish a different Intent MUST be rejected, and Intent.parsed MUST NOT be mutable except via the intent_extension mechanism in ask-details.json.", + "properties": { + "raw": { "type": "string" }, + "parsed": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tool": { "type": "string" }, + "operation": { "type": "string" }, + "resource": { "type": "string" } + } + } + }, + "parser_provenance": { "$ref": "../provenance.json" }, + "scope_mode": { + "type": "string", + "enum": ["strict", "moderate", "permissive"] + } + }, + "if": { "required": ["parsed"] }, + "then": { "required": ["parser_provenance"] } + }, + "platform_context": { + "type": "object", + "description": "Optional platform-specific context for session initialization (e.g., browser session id, IDE workspace path, request origin URL).", + "additionalProperties": true + } + } +} diff --git a/specification/v0.1.0/hooks/subagent-start.json b/specification/v0.1.0/hooks/subagent-start.json new file mode 100644 index 0000000..f17f78a --- /dev/null +++ b/specification/v0.1.0/hooks/subagent-start.json @@ -0,0 +1,63 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/subagent-start.json", + "title": "steps/subagentStart payload", + "description": "Fires when a parent agent spawns a subagent in-process — i.e., delegation that does NOT cross an A2A boundary. A2A-mediated delegation flows through steps/agentTrigger with trigger_type='a2a_inbound' on the subagent's side; subagentStart is for the same-runtime case. The audit chain must record whether the subagent inherits the parent's Intent.parsed, gets a derived intent, or starts fresh — and a Guardian MAY deny the spawn if intent_derivation would grant capabilities the parent's Intent.parsed does not authorize. Sessions are per-subagent: each subagent gets its own session_id and SessionContext; the parent–child relation is captured here.", + "type": "object", + "required": ["subagent_session_id", "parent_session_id", "parent_step_id", "intent_derivation"], + "properties": { + "subagent_session_id": { + "type": "string", + "format": "uuid", + "description": "Fresh session identifier for the spawned subagent. Distinct from the envelope's metadata.session_id, which is the parent's session. The subagent's own steps/* hooks will carry this id in their envelope metadata.session_id." + }, + "parent_session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the parent. Lets cross-session policies and audit replay reconstruct the spawn relationship." + }, + "parent_step_id": { + "type": "string", + "description": "step_id of the parent's hook that triggered the spawn (typically a steps/toolCallRequest for a delegation tool, or a steps/agentResponse that the framework interpreted as a spawn directive)." + }, + "intent_derivation": { + "type": "string", + "enum": ["inherit_full", "inherit_subset", "derived_from_parent", "fresh"], + "description": "How the subagent's Intent.parsed relates to the parent's. 'inherit_full' = identical capability set (subagent can do anything the parent can). 'inherit_subset' = strict subset (subagent_intent.parsed MUST be a subset of parent's parsed; the Guardian SHOULD verify). 'derived_from_parent' = the parent emitted a parsed delegation directive that the framework's intent parser turned into the subagent's intent (parser_provenance MUST cite the parent step_id). 'fresh' = the subagent has its own user-derived Intent (typically only valid when the spawn was triggered by user-routed input)." + }, + "subagent_intent": { + "type": "object", + "description": "The Intent that will govern the subagent. Same shape as the Intent in agent-trigger.json. parser_provenance is REQUIRED — for 'derived_from_parent', parser_provenance.derived_from MUST include the parent_step_id. The Guardian MAY deny the spawn if subagent_intent grants capabilities outside the parent's Intent.parsed when intent_derivation='inherit_subset'.", + "properties": { + "raw": { "type": "string" }, + "parsed": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tool": { "type": "string" }, + "operation": { "type": "string" }, + "resource": { "type": "string" } + } + } + }, + "parser_provenance": { "$ref": "../provenance.json" }, + "scope_mode": { + "type": "string", + "enum": ["strict", "moderate", "permissive"] + } + }, + "if": { "required": ["parsed"] }, + "then": { "required": ["parser_provenance"] } + }, + "subagent_descriptor": { + "type": "object", + "description": "Optional descriptor of the subagent itself — its agent_id, model, framework version, etc. SHOULD be populated when the subagent is a distinct named component the deployment can identify (e.g., a research subagent, a coding subagent).", + "properties": { + "agent_id": { "type": "string" }, + "agent_name": { "type": "string" }, + "model_id": { "type": "string" } + } + } + } +} diff --git a/specification/v0.1.0/hooks/subagent-stop.json b/specification/v0.1.0/hooks/subagent-stop.json new file mode 100644 index 0000000..8b11c89 --- /dev/null +++ b/specification/v0.1.0/hooks/subagent-stop.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/subagent-stop.json", + "title": "steps/subagentStop payload", + "description": "Fires when an in-process subagent terminates. The envelope's metadata.session_id is the PARENT's session (the parent receives the subagent-stop notification on its own audit chain); the subagent's own session has by this point already emitted its sessionEnd with its own final_chain_hash. This hook lets the parent's audit chain reference the subagent's terminal state without merging the two chains. Not decision-eligible — the subagent has already terminated.", + "type": "object", + "required": ["subagent_session_id", "outcome", "final_chain_hash"], + "properties": { + "subagent_session_id": { + "type": "string", + "format": "uuid", + "description": "session_id of the terminated subagent. Matches the subagent_session_id from the originating subagentStart." + }, + "outcome": { + "type": "string", + "enum": ["completed", "failed", "cancelled", "timeout", "denied_at_spawn"], + "description": "Terminal status. 'completed' = subagent returned normally. 'failed' = subagent errored. 'cancelled' = parent or external signal cancelled it. 'timeout' = subagent exceeded its timeout. 'denied_at_spawn' = Guardian denied the original subagentStart; this stop is recorded for audit symmetry even though no subagent ran." + }, + "final_chain_hash": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "The subagent's final SessionContext chain_hash at sessionEnd. Lets parent-side audit replay verify the subagent's chain integrity without the parent maintaining a copy of it." + }, + "summary": { + "type": "object", + "description": "Optional value the subagent returned to the parent. Provenance: origin='agent_generated' and derived_from = the subagent's response lineage. The summary's content becomes data the parent can act on; its effective trust classification is computed by the parent-side Guardian against local policy from the lineage's origins, and MUST not exceed the minimum effective trust of the lineage (monotonicity). v0.1 does not carry a trust field on the wire — see §3.7.", + "required": ["value", "provenance"], + "properties": { + "value": {}, + "provenance": { "$ref": "../provenance.json" } + } + }, + "subagent_step_count": { + "type": "integer", + "minimum": 0, + "description": "Optional. Total steps the subagent executed. Useful for resource-accounting policies." + } + } +} diff --git a/specification/v0.1.0/hooks/system-ping.json b/specification/v0.1.0/hooks/system-ping.json new file mode 100644 index 0000000..e776bc2 --- /dev/null +++ b/specification/v0.1.0/hooks/system-ping.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/system-ping.json", + "title": "system/ping payload", + "description": "Liveness probe. Carries no enforcement semantics. Guardians MUST always return decision='allow' for system/ping regardless of policy, signature, or session state. system/ping MUST NOT be written into the SessionContext audit chain (no ContextEntry, no chain_hash advance) and MUST NOT require a signature even if the session otherwise mandates them, so liveness probing remains possible during signature-rotation or key-resolution failures. Connection failure or response timeout for system/ping is a transport-level signal — the Observed Agent MAY use it to renegotiate transport, re-handshake, or fail over.", + "type": "object", + "properties": { + "echo": { + "type": "string", + "maxLength": 256, + "description": "Optional opaque string the Guardian echoes back in its response payload. Useful for round-trip-time measurement and for distinguishing concurrent probes." + } + } +} diff --git a/specification/v0.1.0/hooks/tool-call-request.json b/specification/v0.1.0/hooks/tool-call-request.json new file mode 100644 index 0000000..3f1e2f2 --- /dev/null +++ b/specification/v0.1.0/hooks/tool-call-request.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/tool-call-request.json", + "title": "steps/toolCallRequest payload", + "description": "Payload for the toolCallRequest hook. Fires before tool execution. Primary enforcement gate.", + "type": "object", + "required": ["tool", "arguments"], + "properties": { + "tool": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string" }, + "version": { "type": "string" }, + "provider": { "type": "string" } + } + }, + "operation": { + "type": "string", + "description": "Optional sub-operation when the tool exposes multiple verbs" + }, + "capability": { + "type": "string", + "description": "Abstract capability identifier (e.g., filesystem.delete, network.egress, process.execute)" + }, + "arguments": { + "type": "object", + "description": "Tool arguments. Each argument value MUST carry its own provenance.", + "additionalProperties": { + "type": "object", + "required": ["value", "provenance"], + "properties": { + "value": {}, + "provenance": { "$ref": "../provenance.json" } + } + } + }, + "raw_command": { + "type": "string", + "description": "Optional shell-style command string for tools that accept one" + }, + "intent": { + "type": "object", + "description": "Optional natural-language framing of why the tool is being called", + "properties": { + "description": { "type": "string" }, + "goal": { "type": "string" } + } + } + } +} diff --git a/specification/v0.1.0/hooks/tool-call-result.json b/specification/v0.1.0/hooks/tool-call-result.json new file mode 100644 index 0000000..84801b5 --- /dev/null +++ b/specification/v0.1.0/hooks/tool-call-result.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/tool-call-result.json", + "title": "steps/toolCallResult payload", + "description": "Payload for the toolCallResult hook. Fires after tool execution, before the result reaches the agent. Output redaction gate.", + "type": "object", + "required": ["tool", "exit_status", "outputs"], + "properties": { + "tool": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string" }, + "version": { "type": "string" }, + "provider": { "type": "string" } + } + }, + "operation": { "type": "string" }, + "request_id_ref": { + "type": "string", + "format": "uuid", + "description": "Links to the originating toolCallRequest" + }, + "exit_status": { + "type": "string", + "enum": ["success", "failure", "timeout", "blocked"] + }, + "outputs": { + "type": "array", + "items": { + "type": "object", + "required": ["value", "provenance"], + "properties": { + "value": {}, + "provenance": { "$ref": "../provenance.json" } + } + } + }, + "duration_ms": { "type": "integer", "minimum": 0 } + } +} diff --git a/specification/v0.1.0/hooks/turn-end.json b/specification/v0.1.0/hooks/turn-end.json new file mode 100644 index 0000000..3dfe406 --- /dev/null +++ b/specification/v0.1.0/hooks/turn-end.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/turn-end.json", + "title": "steps/turnEnd payload", + "description": "Fires at the end of an agent turn. Audit-only — the turn has already happened, so the Guardian writes a ContextEntry but cannot return 'deny' or 'modify'. The matching turnStart's turn_id is referenced via metadata.turn_id on the envelope (and SHOULD also appear in this payload for self-containment). Closes the per-turn audit window so policies that key on per-turn state (cumulative-taint reset, tool-call counts, etc.) have a clean boundary to compute against.", + "type": "object", + "required": ["turn_id", "outcome"], + "properties": { + "turn_id": { + "type": "string", + "description": "Identifier of the turn that just ended. MUST equal the turn_id from the matching turnStart and from metadata.turn_id on this envelope." + }, + "outcome": { + "type": "string", + "enum": ["completed", "deferred", "error", "interrupted", "denied_at_start"], + "description": "Terminal status of the turn. 'completed' = turn finished normally. 'deferred' = a step in this turn returned 'defer' and the turn is paused awaiting resolution; the next turn (or this same turn after resolution) MAY have triggered_by='auto_continuation'. 'error' = the turn errored. 'interrupted' = an external signal (user cancellation, parent-agent recall) ended it early. 'denied_at_start' = the matching turnStart returned 'deny'; this turnEnd is recorded for audit symmetry." + }, + "step_count": { + "type": "integer", + "minimum": 0, + "description": "Number of per-step ContextEntries written during this turn (between turnStart and this turnEnd, exclusive of both). Useful for per-turn rate-limit policies and for resource accounting." + }, + "summary": { + "type": "object", + "description": "Optional summary of what happened in the turn. Provenance: origin='agent_generated' and derived_from = the union of the steps' provenance_ids. (Trust on the resulting summary is computed by the Guardian against local policy from the lineage's origins; v0.1 does not carry a trust field on the wire — see §3.7.) Lets the Guardian record a turn-level summary distinct from any agentResponse content.", + "properties": { + "value": { "type": "string" }, + "provenance": { "$ref": "../provenance.json" } + }, + "required": ["value", "provenance"] + }, + "tool_call_count": { + "type": "integer", + "minimum": 0, + "description": "Optional. Number of toolCallRequest steps this turn. Convenience field for the common 'limit tool calls per turn' policy; also derivable by a Guardian that walks ContextEntries between turnStart and turnEnd." + } + } +} diff --git a/specification/v0.1.0/hooks/turn-start.json b/specification/v0.1.0/hooks/turn-start.json new file mode 100644 index 0000000..dc0814f --- /dev/null +++ b/specification/v0.1.0/hooks/turn-start.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/turn-start.json", + "title": "steps/turnStart payload", + "description": "Fires at the beginning of an agent turn. Lightweight — most policies will return 'allow' and use the hook for state transitions in policy. Decision-eligible (a Guardian MAY deny to block the turn from starting, e.g., to enforce a per-turn rate limit or to refuse auto-continuation under certain conditions). The framework MUST set metadata.turn_id on the envelope and propagate that turn_id onto every subsequent per-step ContextEntry until the matching turnEnd. Without an explicit turn boundary, AARM-style 'no consequential action in N turns after taint' has to infer turn breaks from userMessage/agentResponse pairing — an inference that breaks under auto-continuation, planning loops, and multi-step ReAct cycles.", + "type": "object", + "required": ["turn_id", "triggered_by"], + "properties": { + "turn_id": { + "type": "string", + "description": "Identifier for this turn. SHOULD equal the request_id of this turnStart envelope. Carried forward on metadata.turn_id of every step until turnEnd." + }, + "triggered_by": { + "type": "string", + "enum": ["user_message", "auto_continuation", "agent_loop", "subagent_return", "scheduled", "external_event"], + "description": "What opened this turn. 'user_message' = a userMessage was just received and the agent will respond. 'auto_continuation' = a Stop hook elsewhere returned a continuation directive. 'agent_loop' = the agent decided on its own to start another turn (planning loop, ReAct, etc.). 'subagent_return' = a subagent returned and the parent is resuming. 'scheduled' / 'external_event' = a non-user trigger fired." + }, + "parent_turn_id": { + "type": "string", + "description": "Optional. Identifier of the enclosing turn when turns nest. Typical case: a parent agent is in the middle of a turn that spawns a subagent (which has its own session and its own turn) and is awaiting the subagent's return; the subagent's first turn carries parent_turn_id pointing at the parent's open turn." + }, + "expected_inputs": { + "type": "array", + "items": { "type": "string" }, + "description": "Optional. Hint about what the framework expects to happen during this turn (e.g., ['userMessage','toolCallRequest','agentResponse']). Lets policies prepare expectations and detect unexpected sequences." + } + } +} diff --git a/specification/v0.1.0/hooks/user-message.json b/specification/v0.1.0/hooks/user-message.json new file mode 100644 index 0000000..fa38a06 --- /dev/null +++ b/specification/v0.1.0/hooks/user-message.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/hooks/user-message.json", + "title": "steps/userMessage payload", + "description": "Payload for the userMessage hook. Fires when user input arrives, before LLM ingestion.", + "type": "object", + "required": ["content"], + "properties": { + "content": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["type", "value", "provenance"], + "properties": { + "type": { "type": "string", "enum": ["text", "image", "audio", "file"] }, + "value": {}, + "provenance": { "$ref": "../provenance.json" } + } + } + } + } +} diff --git a/specification/v0.1.0/inspect/format-mapping.json b/specification/v0.1.0/inspect/format-mapping.json new file mode 100644 index 0000000..77021c4 --- /dev/null +++ b/specification/v0.1.0/inspect/format-mapping.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/inspect/format-mapping.json", + "title": "ACS AgBOM Serialization Format Mapping", + "description": "Normative mapping from the canonical AgBOM (agbom/document.json) into the three v0.1.0 serialization targets: CycloneDX 1.6, SPDX 3.0, and SWID (ISO/IEC 19770-2). This is a documentation-shaped JSON Schema — its body describes the transformation rules; conformant Guardians MUST be able to render at least one of these on request from a downstream consumer.", + "type": "object", + "properties": { + "cyclonedx_1_6": { + "type": "object", + "description": "CycloneDX 1.6 JSON. Each canonical component → one entry in components[]. The CycloneDX bom-ref equals the canonical component id. Type mapping: model → 'machine-learning-model' (CycloneDX bom-types extension); mcp_server, a2a_peer, knowledge_source → 'service'; tool → 'application'; memory_store → 'data' (with type='database' subtype where applicable); agent_capability → represented in CycloneDX 'composition' aggregating its referenced components. The CycloneDX 'metadata.tools' SHOULD include the Observed Agent's framework as the BOM generator. Provenance attached to a component MUST be carried into CycloneDX 'evidence' or 'properties' (acs:provenance:origin, and acs:provenance:source_id when present). v0.1 carries factual provenance only; trust classification is computed by the Guardian against local policy and is not exported in CycloneDX (see §3.7)." + }, + "spdx_3_0": { + "type": "object", + "description": "SPDX 3.0 JSON-LD. Each canonical component → one Element node. Type mapping: model → spdx:AIPackage (SPDX 3.0 AI profile); tool → spdx:SoftwarePackage; mcp_server, a2a_peer, knowledge_source → spdx:Service; memory_store → spdx:DatasetPackage; agent_capability → spdx:Bundle aggregating its referenced components via 'contains' relationships. The graph is wired with SPDX relationships: agent_capability--CONTAINS-->{tool,mcp_server,a2a_peer}; mcp_server--EXPOSES-->tool. AgBOM-level metadata (agent.id, generated_at, agbom_hash) maps to spdx:CreationInfo and spdx:Element identifiers." + }, + "swid": { + "type": "object", + "description": "SWID (ISO/IEC 19770-2) XML. Each canonical component → one SoftwareIdentity tag with @tagId = component id, @name = component name, @version = component version. Provider maps to . agent_capability components emit a SoftwareIdentity with @corpus='true' and use to reference their constituent components. Endpoint URIs map to . The AgBOM-level agent and timestamps populate the root wrapping the per-component tags." + } + } +} diff --git a/specification/v0.1.0/modifications.json b/specification/v0.1.0/modifications.json new file mode 100644 index 0000000..41fed0f --- /dev/null +++ b/specification/v0.1.0/modifications.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/modifications.json", + "title": "ACS Modifications", + "description": "Modifications applied when decision is 'modify'. Includes redactions (decision was 'redact' in earlier drafts).", + "type": "object", + "minProperties": 1, + "properties": { + "modified_content": { + "type": "string", + "description": "Replacement content for the original payload" + }, + "redactions": { + "type": "array", + "description": "Targeted redactions applied to specific fields", + "items": { + "type": "object", + "required": ["path"], + "properties": { + "path": { "type": "string", "description": "JSON pointer to the field" }, + "replacement": { "type": "string", "description": "Replacement text (default '[REDACTED]')" } + } + } + }, + "parameter_overrides": { + "type": "object", + "description": "Replacement values for tool call arguments", + "additionalProperties": true + } + } +} diff --git a/specification/v0.1.0/provenance-summary.json b/specification/v0.1.0/provenance-summary.json new file mode 100644 index 0000000..c7372c8 --- /dev/null +++ b/specification/v0.1.0/provenance-summary.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/provenance-summary.json", + "title": "ACS Provenance Summary", + "description": "Condensed view of provenance facts for cumulative or session-level policy decisions. Computed by Guardian framework code from the Provenance objects observed at a step (entry-level summary) or across the session to date (session-level summary). All fields are OPTIONAL — Guardians populate the fields their policies actually consume. Used by paradigms that reason over accumulated context (AARM cumulative-context, FIDES session-level integrity); paradigms that don't (e.g., pure IBAC) can omit the entire object. v0.1 summaries carry origin-derived aggregates only; trust-derived aggregates (e.g., lowest_trust, earliest_untrusted_step_id) are Guardian-internal in v0.1 because trust classification is computed in policy from origin/source_id, not carried on the wire (§3.7).", + "type": "object", + "properties": { + "origins_seen": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "user_input", + "system", + "tool_output", + "retrieved", + "agent_generated", + "a2a_inbound", + "external" + ] + }, + "description": "Distinct origins observed in the scope of this summary. The primary v0.1 signal for cumulative-context and session-integrity policies — Guardians map origins (and source_ids) to trust internally per local policy." + }, + "entry_count": { + "type": "integer", + "minimum": 0, + "description": "Total number of Provenance objects (entry-level) or ContextEntries (session-level) summarized." + }, + "entry_count_by_origin": { + "type": "object", + "additionalProperties": { "type": "integer", "minimum": 0 }, + "description": "Map of origin => count of Provenance objects with that origin in scope. Keys MUST be drawn from the origin enum. Optional; used by policies that distinguish, e.g., a single tool_output from many." + }, + "earliest_step_id_by_origin": { + "type": "object", + "additionalProperties": { "type": "string" }, + "description": "Map of origin => step_id of the earliest step at which data of that origin entered the scope. Optional; used by policies expressed as 'deny if entered within the last N turns' to compute lookback distance from the current step. Replaces the trust-derived earliest_untrusted_step_id from earlier drafts: v0.1 carries origin facts, the Guardian projects them onto its trust classification in policy." + }, + "max_lineage_depth": { + "type": "integer", + "minimum": 0, + "description": "Longest derived_from chain depth observed. Optional; used by policies that constrain lineage depth (e.g., 'deny if data is more than K hops from a policy-trusted source')." + } + } +} diff --git a/specification/v0.1.0/provenance.json b/specification/v0.1.0/provenance.json new file mode 100644 index 0000000..e51f965 --- /dev/null +++ b/specification/v0.1.0/provenance.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/provenance.json", + "title": "ACS Provenance", + "description": "Factual lineage label attached to data-bearing fields. Populated by deterministic framework code at channel boundaries, never by the LLM. Provenance attachment is OPTIONAL at the field level; when a Provenance object is emitted, all required fields below MUST be populated. v0.1 carries factual provenance (origin, source_id, derived_from) on the wire; trust classification is performed by the Guardian against local policy keyed on origin and source_id, and is not a v0.1 schema field. The spec (§3.7) reserves an OPTIONAL `trust` enum for vendor implementations that elect to carry classification on the wire; such implementations extend this schema rather than rely on v0.1 to validate the field.", + "type": "object", + "required": ["provenance_id", "origin"], + "properties": { + "provenance_id": { + "type": "string", + "description": "Unique identifier for this provenance node within the session" + }, + "origin": { + "type": "string", + "enum": [ + "user_input", + "system", + "tool_output", + "retrieved", + "agent_generated", + "a2a_inbound", + "external" + ], + "description": "Where this data entered the system" + }, + "source_id": { + "type": "string", + "description": "Identifier within the origin (tool name, URL, file path, retrieval source). Combined with origin, this is what Guardians key trust classification on in v0.1." + }, + "derived_from": { + "type": "array", + "items": { "type": "string" }, + "description": "Lineage edges. Array of provenance_ids this data was derived from." + } + } +} diff --git a/specification/v0.1.0/request-envelope.json b/specification/v0.1.0/request-envelope.json new file mode 100644 index 0000000..809c17d --- /dev/null +++ b/specification/v0.1.0/request-envelope.json @@ -0,0 +1,130 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/request-envelope.json", + "title": "ACS Request Envelope", + "description": "JSON-RPC 2.0 envelope with ACS extensions. Sent by the Observed Agent to the Guardian Agent.", + "type": "object", + "required": ["jsonrpc", "method", "id", "params"], + "additionalProperties": false, + "properties": { + "jsonrpc": { "const": "2.0" }, + "method": { + "type": "string", + "description": "Hook or wrapped-protocol method name. Reserved namespaces: steps/* (Instrument hooks), protocols/* (wrapped sub-protocols MCP and A2A), agbom/* (Inspect pillar AgBOM methods), trace/* (reserved for future ACS-native trace methods; v0.1 does not define any), system/* (low-level transport/control methods, including system/ping for liveness), handshake/* (capability negotiation, including handshake/hello), wrapped: (explicit-version wrapped form, e.g. wrapped:a2a-0.2/message/send).", + "pattern": "^(steps/|protocols/|agbom/|trace/|system/|handshake/|wrapped:).+" + }, + "id": { + "oneOf": [{ "type": "string" }, { "type": "number" }], + "description": "JSON-RPC correlation id" + }, + "params": { "$ref": "#/$defs/AcsParams" } + }, + "$defs": { + "AcsParams": { + "type": "object", + "required": ["acs_version", "request_id", "timestamp", "metadata", "payload"], + "properties": { + "acs_version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "ACS specification version (semver)" + }, + "request_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for this hook invocation" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when the hook fired" + }, + "nonce": { + "type": "string", + "minLength": 16, + "maxLength": 64, + "description": "Cryptographic nonce for replay prevention" + }, + "tenant_id": { + "type": "string", + "description": "Tenant identifier (multi-tenancy v0.1: reserved field, no isolation rules)" + }, + "metadata": { "$ref": "#/$defs/Metadata" }, + "payload": { + "type": "object", + "description": "Hook-specific payload. Schema depends on method." + }, + "signature": { "$ref": "#/$defs/Signature" } + } + }, + "Metadata": { + "type": "object", + "required": ["agent_id", "session_id"], + "properties": { + "agent_id": { "type": "string" }, + "agent_name": { "type": "string" }, + "session_id": { "type": "string", "format": "uuid" }, + "turn_id": { + "type": "string", + "description": "Identifier of the agent turn this step belongs to. Set by the framework when the turn opens (steps/turnStart) and propagated onto every per-step ContextEntry until steps/turnEnd. Lets policies key on per-turn state ('limit tool calls per turn', 'no consequential action in N turns after taint') without inferring turn boundaries from userMessage/agentResponse pairing — the inference breaks under auto-continuation, planning loops, and multi-step ReAct cycles. Required on every step between a turnStart and the matching turnEnd; absent on session-level steps (sessionStart, sessionEnd, agentTrigger, agbom/* events outside a turn)." + }, + "parent_turn_id": { + "type": "string", + "description": "Optional. Identifier of the enclosing turn when turns nest (e.g., a subagent turn inside a parent turn that is awaiting subagentStop). The parent–child turn relation is also captured in turnStart's payload; this field is the wire-level shortcut so per-step audit entries don't need to walk the chain to find the enclosing turn." + }, + "session_state": { + "type": "object", + "description": "Optional session state hints from the Observed Agent. The Guardian holds the authoritative state.", + "properties": { + "chain_hash": { "type": "string", "description": "Latest known chain hash from the agent's perspective" } + } + }, + "environment": { + "type": "string", + "enum": ["development", "staging", "production"] + }, + "platform": { "type": "string" }, + "platform_version": { "type": "string" }, + "user_context": { + "type": "object", + "properties": { + "user_id": { "type": "string" }, + "roles": { "type": "array", "items": { "type": "string" } }, + "authentication_method": { "type": "string" } + } + } + } + }, + "Signature": { + "type": "object", + "required": ["algorithm", "value", "key_id"], + "description": "Cryptographic signature envelope. The registry is crypto-agile: HMAC-SHA256 is the v0.1 baseline RECOMMENDED default (sufficient for shared-secret deployments). Classical asymmetric (ECDSA-P256, RSA-PSS-SHA256) and PQC algorithms (ML-DSA-*, SLH-DSA-*, aligned to NIST FIPS 203-205) are available under the ACS-Crypto profile. ML-DSA-65 is the RECOMMENDED primary for ACS-Crypto deployments; SLH-DSA-128s is a SHOULD backup for algorithmic diversity. Hybrid composites combine PQC + classical for transitional deployments.", + "properties": { + "algorithm": { + "type": "string", + "enum": [ + "HMAC-SHA256", + "ECDSA-P256", + "RSA-PSS-SHA256", + "ML-DSA-65", + "ML-DSA-44", + "ML-DSA-87", + "SLH-DSA-128s", + "SLH-DSA-128f", + "ML-DSA-65+ECDSA-P256", + "ML-DSA-65+RSA-PSS-SHA256" + ], + "description": "Signature algorithm. HMAC-SHA256 is symmetric MAC for shared-secret deployments (v0.1 RECOMMENDED). ECDSA-P256 and RSA-PSS-SHA256 are classical asymmetric. ML-DSA-* and SLH-DSA-* are pure PQC. The '+' forms are hybrid composites: the value field is the base64 of len(pqc_sig)||pqc_sig||len(classical_sig)||classical_sig where each len is a 4-byte big-endian unsigned integer; verifiers MUST verify both component signatures over the same canonical input." + }, + "value": { + "type": "string", + "description": "Base64-encoded signature. For hybrid algorithms, see the algorithm description for the composite encoding." + }, + "key_id": { + "type": "string", + "description": "Identifier resolving to the public key (or the hybrid key descriptor that pins both component public keys for hybrid algorithms)." + } + } + } + } +} diff --git a/specification/v0.1.0/response-envelope.json b/specification/v0.1.0/response-envelope.json new file mode 100644 index 0000000..6e1a1cd --- /dev/null +++ b/specification/v0.1.0/response-envelope.json @@ -0,0 +1,110 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/response-envelope.json", + "title": "ACS Response Envelope", + "description": "JSON-RPC 2.0 response envelope with ACS extensions. Sent by the Guardian Agent to the Observed Agent.", + "type": "object", + "required": ["jsonrpc", "id"], + "additionalProperties": false, + "properties": { + "jsonrpc": { "const": "2.0" }, + "id": { "oneOf": [{ "type": "string" }, { "type": "number" }] }, + "result": { "$ref": "#/$defs/AcsResult" }, + "error": { "$ref": "#/$defs/JsonRpcError" } + }, + "oneOf": [ + { "required": ["result"], "not": { "required": ["error"] } }, + { "required": ["error"], "not": { "required": ["result"] } } + ], + "$defs": { + "AcsResult": { + "type": "object", + "required": ["type", "acs_version", "request_id", "decision"], + "properties": { + "type": { + "type": "string", + "const": "final", + "description": "Discriminator. v0.1 only emits 'final'. v0.2 adds 'progress' and 'interruption'." + }, + "acs_version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" }, + "request_id": { "type": "string", "format": "uuid" }, + "decision": { + "type": "string", + "enum": ["allow", "deny", "modify", "ask", "defer"] + }, + "reasoning": { + "type": "string", + "description": "Human-renderable explanation of the verdict. REQUIRED on 'deny', 'modify', 'ask', and 'defer' (these decisions affect or interrupt the agent's action and the operator/end-user needs an explanation). RECOMMENDED on 'allow' when the deployment expects user-visible audit trails. This is the single explanation field — it serves both end-user display and audit/agent-internal consumption; deployments that want different text for those audiences SHOULD compose them client-side from reasoning + policy_data + reason_codes rather than expecting separate fields here." + }, + "reason_codes": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "description": "Optional machine-readable categorization of why the decision was reached. Free vocabulary in v0.1 (a registry MAY be layered in a future version once patterns stabilize). Common categories that fall out of the v0.1 paradigms: 'fides_p_t_violation', 'fides_p_f_violation', 'untrusted_into_consequential', 'ibac_intent_mismatch', 'ibac_scope_mode_strict', 'aarm_cumulative_taint', 'camel_dependency_graph_violation', 'pii_detected', 'rate_limited'. UIs and meta-policies SHOULD switch on these codes rather than parsing reasoning text or policy/rule ids (which are bundle-specific)." + }, + "policy_references": { + "type": "array", + "items": { + "type": "object", + "properties": { + "policy_id": { "type": "string" }, + "policy_name": { "type": "string" }, + "rule_id": { "type": "string" } + } + }, + "description": "The policies/rules that fired. A single decision MAY cite multiple — when IBAC + FIDES + AARM all reject the same action, expect three entries here, one per paradigm. Audit replay walks this list to reconstruct which rules contributed." + }, + "policy_data": { + "type": "object", + "additionalProperties": true, + "description": "Paradigm-specific or policy-specific structured payload. Free-form escape hatch for facts the policy wants to surface beyond reasoning text — e.g., FIDES exposes the violating argument path and lineage; IBAC exposes the requested capability and closest Intent.parsed match; AARM exposes the earliest policy-untrusted step_id and lookback_distance computed by the Guardian (v0.1 carries factual provenance on the wire and the Guardian projects it onto its trust classification — see §3.7). When multiple paradigms fire on the same decision, the convention is to key by paradigm name: { 'ibac': {...}, 'fides': {...}, 'aarm': {...} }, so each payload is independently parseable. Unstructured 'data' from earlier ACS drafts maps here." + }, + "cited_provenance_ids": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "description": "Optional. The provenance_ids whose facts drove this decision. FIDES P-T denials cite the lineage edge that violated trust; CaMeL violations cite the dependency-graph node; AARM cites the earliest untrusted step's provenance; IBAC 'allow with possibly-tainted contents' decisions cite the lineage that justified the call. Lifting this to a standard top-level field gives audit tooling one place to look for 'which provenance objects drove this verdict' without parsing per-paradigm policy_data payloads, and lets a verifier walk derived_from chains from these ids to confirm the verdict is consistent with the chain." + }, + "modifications": { "$ref": "modifications.json" }, + "ask_details": { "$ref": "ask-details.json" }, + "defer_details": { "$ref": "defer-details.json" }, + "payload": { + "type": "object", + "description": "Method-specific response data. Used by system/* methods (e.g., system/ping returns { status, echo, server_timestamp }) and other methods that carry structured data alongside the decision. Not used by standard Instrument hooks — those communicate via modifications, ask_details, or defer_details." + }, + "metadata": { + "type": "object", + "description": "ACS-defined evaluator and observability metadata. NOT for policy-emitted facts (use policy_data for those) — keeping the split clean lets Trace consumers key on a stable shape.", + "properties": { + "evaluator": { + "type": "string", + "enum": ["deterministic", "agent", "composite"] + }, + "evaluator_version": { "type": "string" }, + "evaluation_duration_ms": { "type": "integer", "minimum": 0 }, + "model_id": { "type": "string", "description": "Required if evaluator is 'agent' or 'composite'" }, + "confidence": { "type": "number", "minimum": 0, "maximum": 1 } + } + } + }, + "allOf": [ + { "if": { "properties": { "decision": { "const": "deny" } }, "required": ["decision"] }, "then": { "required": ["reasoning"] } }, + { "if": { "properties": { "decision": { "const": "modify" } }, "required": ["decision"] }, "then": { "required": ["reasoning", "modifications"] } }, + { "if": { "properties": { "decision": { "const": "ask" } }, "required": ["decision"] }, "then": { "required": ["reasoning", "ask_details"] } }, + { "if": { "properties": { "decision": { "const": "defer" } }, "required": ["decision"] }, "then": { "required": ["reasoning", "defer_details"] } } + ] + }, + "JsonRpcError": { + "type": "object", + "required": ["code", "message"], + "properties": { + "code": { + "type": "integer", + "description": "JSON-RPC error code. ACS reserves -32000 to -32099 for application errors." + }, + "message": { "type": "string" }, + "data": {} + } + } + } +} diff --git a/specification/v0.1.0/trace/ocsf-mapping.json b/specification/v0.1.0/trace/ocsf-mapping.json new file mode 100644 index 0000000..76c4e20 --- /dev/null +++ b/specification/v0.1.0/trace/ocsf-mapping.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/trace/ocsf-mapping.json", + "title": "ACS Trace → OCSF event-class mapping", + "description": "Normative mapping from ACS hooks and decisions to OCSF (Open Cybersecurity Schema Framework) event classes. v0.1.0 conformance requires that a deployment emitting OCSF for the Trace pillar uses these class UIDs and severity rules verbatim. OCSF classes are referenced by their stable class_uid as of OCSF 1.5+.", + "type": "object", + "properties": { + "step_to_class": { + "type": "object", + "description": "Map ACS step methods to OCSF class UIDs.", + "additionalProperties": { + "type": "object", + "required": ["class_uid", "class_name"], + "properties": { + "class_uid": { "type": "integer" }, + "class_name": { "type": "string" }, + "activity_id": { "type": "integer", "description": "OCSF activity_id within the class (e.g., 1=Logon, 2=Logoff for Authentication)." } + } + }, + "default": { + "steps/sessionStart": { "class_uid": 3002, "class_name": "Authentication", "activity_id": 1 }, + "steps/sessionEnd": { "class_uid": 3002, "class_name": "Authentication", "activity_id": 2 }, + "steps/userMessage": { "class_uid": 6002, "class_name": "Application Activity" }, + "steps/agentResponse": { "class_uid": 6002, "class_name": "Application Activity" }, + "steps/agentTrigger": { "class_uid": 6002, "class_name": "Application Activity" }, + "steps/turnStart": { "class_uid": 6002, "class_name": "Application Activity" }, + "steps/turnEnd": { "class_uid": 6002, "class_name": "Application Activity" }, + "steps/toolCallRequest": { "class_uid": 1007, "class_name": "Process Activity" }, + "steps/toolCallResult": { "class_uid": 1007, "class_name": "Process Activity" }, + "steps/knowledgeRetrieval": { "class_uid": 6005, "class_name": "Datastore Activity" }, + "steps/memoryStore": { "class_uid": 6005, "class_name": "Datastore Activity" }, + "steps/memoryContextRetrieval": { "class_uid": 6005, "class_name": "Datastore Activity" }, + "steps/preCompact": { "class_uid": 6005, "class_name": "Datastore Activity", "activity_id": 99, "_note": "Compaction is a derived-data write into the agent's working context — modeled as Datastore Activity with a custom activity_id for the compaction subtype." }, + "steps/postCompact": { "class_uid": 6005, "class_name": "Datastore Activity", "activity_id": 99 }, + "steps/subagentStart": { "class_uid": 3002, "class_name": "Authentication", "activity_id": 1, "_note": "Subagent spawn opens a new session — modeled as Authentication/Logon for OCSF symmetry with sessionStart." }, + "steps/subagentStop": { "class_uid": 3002, "class_name": "Authentication", "activity_id": 2 }, + "agbom/snapshot": { "class_uid": 5001, "class_name": "Inventory Info" }, + "agbom/changed": { "class_uid": 5001, "class_name": "Inventory Info" } + } + }, + "decision_class": { + "type": "object", + "description": "Decisions (deny/modify/ask/defer) are emitted as Detection Finding events. 'allow' is normally emitted as Application Activity with informational severity rather than as a finding, unless the deployment elects to record allows as findings for full audit symmetry.", + "properties": { + "class_uid": { "type": "integer", "const": 2004 }, + "class_name": { "type": "string", "const": "Detection Finding" }, + "required_fields": { + "type": "array", + "items": { "type": "string" }, + "default": ["finding.title", "finding.uid", "evidences", "verdict_id"] + } + } + }, + "severity_mapping": { + "type": "object", + "description": "OCSF severity_id values for decision events, keyed by ACS disposition.", + "additionalProperties": { "type": "integer", "minimum": 0, "maximum": 6 }, + "default": { + "allow": 1, + "modify": 2, + "ask": 3, + "defer": 3, + "deny": 4 + } + }, + "provenance_metadata": { + "type": "object", + "description": "When Provenance is attached to the originating hook, the OCSF event MUST carry provenance facts in 'enrichments' (an OCSF-recognized extensibility field). Each enrichment carries name='acs_provenance_*' and value={origin, source_id, ...}. v0.1 carries factual provenance in enrichments; trust classification is computed by the Guardian against local policy and is not a v0.1 enrichment (see §3.7).", + "properties": { + "required_enrichments": { + "type": "array", + "items": { "type": "string" }, + "default": ["acs_provenance_origin"] + }, + "optional_enrichments": { + "type": "array", + "items": { "type": "string" }, + "default": ["acs_provenance_source_id", "acs_provenance_lineage_depth"] + } + } + } + } +} diff --git a/specification/v0.1.0/trace/otel-mapping.json b/specification/v0.1.0/trace/otel-mapping.json new file mode 100644 index 0000000..5c5c21a --- /dev/null +++ b/specification/v0.1.0/trace/otel-mapping.json @@ -0,0 +1,136 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://acs.org/schema/v0.1.0/trace/otel-mapping.json", + "title": "ACS Trace → OpenTelemetry semconv mapping", + "description": "Normative mapping from ACS hooks and decisions to OpenTelemetry spans and span events. v0.1.0 conformance requires that a deployment emitting OTel for the Trace pillar uses these span names and required attributes verbatim. Decisions are recorded as span events on the parent step span, not as separate spans, so that the enforcement verdict and the action it gates share a parent and an OTel context.", + "type": "object", + "properties": { + "step_to_span": { + "type": "object", + "description": "Map ACS step methods to OTel span names + required attributes.", + "additionalProperties": { + "type": "object", + "required": ["span_name", "required_attributes"], + "properties": { + "span_name": { "type": "string" }, + "required_attributes": { "type": "array", "items": { "type": "string" } }, + "optional_attributes": { "type": "array", "items": { "type": "string" } } + } + }, + "default": { + "steps/sessionStart": { + "span_name": "acs.session", + "required_attributes": ["acs.session.id"], + "optional_attributes": ["acs.tenant_id", "acs.user.id", "acs.policy_mode"] + }, + "steps/agentTrigger": { + "span_name": "acs.agent.trigger", + "required_attributes": ["acs.agent.id", "acs.trigger.type"] + }, + "steps/userMessage": { + "span_name": "acs.message.user", + "required_attributes": ["acs.session.id", "acs.content.types"] + }, + "steps/agentResponse": { + "span_name": "acs.message.agent", + "required_attributes": ["acs.session.id", "acs.agent.id"] + }, + "steps/toolCallRequest": { + "span_name": "gen_ai.tool.call", + "required_attributes": ["gen_ai.tool.name", "acs.capability"], + "optional_attributes": ["acs.tool.provider", "acs.tool.version", "acs.operation"] + }, + "steps/toolCallResult": { + "span_name": "gen_ai.tool.result", + "required_attributes": ["gen_ai.tool.name", "acs.exit_status"], + "optional_attributes": ["acs.duration_ms", "acs.request_id_ref"] + }, + "steps/knowledgeRetrieval": { + "span_name": "acs.knowledge.retrieval", + "required_attributes": ["acs.source.type", "acs.results.count"] + }, + "steps/memoryStore": { + "span_name": "acs.memory.store", + "required_attributes": ["acs.memory_store.name", "acs.operation"] + }, + "steps/memoryContextRetrieval": { + "span_name": "acs.memory.retrieval", + "required_attributes": ["acs.memory_store.name", "acs.results.count"] + }, + "steps/sessionEnd": { + "span_name": "acs.session.end", + "required_attributes": ["acs.session.reason"] + }, + "steps/turnStart": { + "span_name": "acs.turn", + "required_attributes": ["acs.turn.id", "acs.turn.triggered_by"], + "optional_attributes": ["acs.turn.parent_id"] + }, + "steps/turnEnd": { + "span_name": "acs.turn.end", + "required_attributes": ["acs.turn.id", "acs.turn.outcome"], + "optional_attributes": ["acs.turn.step_count"] + }, + "steps/preCompact": { + "span_name": "acs.compact", + "required_attributes": ["acs.compact.entry_count", "acs.compact.triggered_by"], + "optional_attributes": ["acs.compact.estimated_input_tokens"] + }, + "steps/postCompact": { + "span_name": "acs.compact.complete", + "required_attributes": ["acs.compact.entry_count"], + "optional_attributes": ["acs.compact.lineage_depth_after", "acs.provenance.origins_compacted"] + }, + "steps/subagentStart": { + "span_name": "acs.subagent", + "required_attributes": ["acs.subagent.session_id", "acs.subagent.parent_session_id", "acs.subagent.intent_derivation"] + }, + "steps/subagentStop": { + "span_name": "acs.subagent.end", + "required_attributes": ["acs.subagent.session_id", "acs.subagent.outcome", "acs.subagent.final_chain_hash"] + }, + "agbom/snapshot": { + "span_name": "acs.agbom", + "required_attributes": ["acs.agbom.format", "acs.agbom.component_count"] + }, + "agbom/changed": { + "span_name": "acs.agbom", + "required_attributes": ["acs.agbom.format", "acs.agbom.change_reason"] + } + } + }, + "decision_event": { + "type": "object", + "description": "Decisions are recorded as a span event named 'acs.decision' on the parent step span. The event MUST carry the disposition and evaluator; reasoning and confidence are required when present in the decision envelope.", + "properties": { + "event_name": { "type": "string", "const": "acs.decision" }, + "required_attributes": { + "type": "array", + "items": { "type": "string" }, + "default": ["acs.decision", "acs.evaluator"] + }, + "conditional_attributes": { + "type": "array", + "items": { "type": "string" }, + "default": ["acs.reasoning", "acs.confidence", "acs.evaluator_version", "acs.model_id"] + } + } + }, + "provenance_attributes": { + "type": "object", + "description": "When Provenance is attached to a hook payload, the resulting span MUST carry provenance facts as attributes. Lineage edges MAY be linked via OTel span links keyed by provenance_id. v0.1 emits the factual provenance fields (origin, source_id, lineage_depth) on the wire and on the span; trust classification is computed by the Guardian against local policy and is not a v0.1 span attribute (see §3.7).", + "properties": { + "required": { + "type": "array", + "items": { "type": "string" }, + "default": ["acs.provenance.origin"] + }, + "optional": { + "type": "array", + "items": { "type": "string" }, + "default": ["acs.provenance.lineage_depth", "acs.provenance.source_id"] + } + } + } + } +}