JOEY VICTORINO Technical Operations & Intelligence

Sample · Architecture Decision Record

Tool definitions are signed, and the runtime verifies them before an agent may call one.

Below is the format and standard of the architecture decision record I write when a design choice has to survive teams I do not manage. Each record states what was decided, what was rejected and why, what the decision costs, and how anyone can verify it is still true. This one records the execution boundary for agent runtimes described in Agents Act Only Through Signed Tools.

Illustrative sample. This record is written to show the format and standard of the deliverable. Its technical content mirrors the public decision recorded in the open-source assay repository (ADR 0003 and the trust-store README), restated in the structure I use with teams. It describes no client system, and the alternatives section is illustrative reasoning, not a transcript of any real review.

ADR · Agent runtime · Tool execution boundary

Status

Accepted. Depends on the canonical JSON decision (digests and signatures are computed over RFC 8785 bytes). Feeds the policy decision (deny wins; default deny), which consumes this record's verification result as its first input. Supersedes nothing.

Context

Models choose tools by name from the manifests they are shown. A manifest declares the tool's capabilities, its risk tier, its input schema, and the agents allowed to call it. Those fields are the inputs the policy engine reasons over.

  • If a manifest can be edited between review and execution, its declared risk tier can be lowered or its capabilities widened without anyone noticing, and the policy engine reasons over false inputs. The same exposure applies to the scope document that authorizes targets.
  • The runtime executes in CI and on developer machines. Key material must be simple to provision and to audit, and verification must not depend on a network service.
  • Any party, in any language, must be able to recompute the bytes that were signed. A language's default JSON encoder is not a specification.
  • Provenance and authorization are different questions. Conflating them means a key rotation becomes a policy change, and a policy change becomes a re-signing exercise.

Decision

  • D1. Every tool manifest carries signer, signed_at, and signature. The signature is Ed25519 over the RFC 8785 canonical bytes of the manifest with signature cleared. The signer id is derived from SHA-256 over the raw public key.
  • D2. The trust store is a directory of public-key files, one per trusted signer, holding public keys only. Private keys are never committed. Loading an empty trust store is an error: a verifier with no trusted keys fails closed.
  • D3. Verification returns exactly one reason code from a closed set: UNSIGNED, SIGNATURE_INVALID, UNKNOWN_SIGNER, PROVENANCE_VALID.
  • D4. A valid signature is provenance, not authorization. The policy engine takes the verification result as its first input and denies on anything but PROVENANCE_VALID; every other policy check runs after that.
  • D5. The same signing scheme applies to arbitrary JSON documents, so scope documents are signed and verified identically.
  • D6. Signing and verification are command-line operations. The verify command exits non-zero on any failure so CI can gate on it. The private key is written with owner-only permissions and is excluded from version control by pattern.

Alternatives considered

  • A1. Repository review alone. Treat manifests as code and rely on pull-request review. Rejected: review establishes what was approved, not what the runtime loaded. Nothing detects a change between merge and execution, and the runtime has no way to know.
  • A2. Content hashes pinned in configuration. Detects modification but not origin: a pin list is itself an unsigned file, so it becomes the thing to edit. It also makes every manifest change a configuration change, which in practice gets batched and skipped.
  • A3. A remote signing or attestation service. Rejected because verification must work offline in CI and on laptops. A network dependency in the authorization path creates pressure to fail open when the service is unreachable, which is the wrong direction for this boundary.
  • A4. Transport security only. Protects a manifest in flight between systems. Says nothing about the file at rest, at review time, or after a local edit, which is where the threat in the Context section lives.

Consequences

  • Tampering with any manifest field after signing is detected, including fields the policy engine does not currently read.
  • Key rotation is adding a public-key file, re-signing, verifying, and removing the old file. There is no revocation protocol beyond removal; manifests signed by a removed key fail with UNKNOWN_SIGNER, which is the intended outcome.
  • Manifests must canonicalize, so input schemas may not contain non-integer numbers. Fractional quantities are stored as integers in a fixed unit.
  • The signature does not authorize. A signed manifest can still describe a dangerous tool; deciding whether an agent may call it is the policy engine's job, and that decision is recorded separately.
  • Teams that adopt this record inherit a CI gate they cannot pass by editing a manifest. That is the point, and it is also the friction they will notice first.

Verification

A decision is accepted only with the checks that show it is still true. For this record:

  • V1. CI runs the verify command over the manifests directory against the committed trust store on every change and fails the build on any non-zero exit.
  • V2. A manifest with a single altered byte after signing verifies as SIGNATURE_INVALID; a manifest signed by a key absent from the trust store verifies as UNKNOWN_SIGNER; a manifest with no signature verifies as UNSIGNED.
  • V3. Loading an empty trust store is an error, and a verifier constructed without trusted keys refuses to run rather than accepting everything.
  • V4. The policy engine denies on any verification result other than PROVENANCE_VALID before evaluating agent, tool, tier, or rules.
  • V5. Every tool call writes a policy decision record to the audit log, so the verification outcome for each call is reconstructible from the log without access to any prompt or response text.
  • V6. The canonical bytes are recomputable from the RFC alone; the RFC's published test vectors are unit tests.

Reference implementation

The public decision this sample mirrors is assay ADR 0003, with the trust-store format in trust/keys/README.md and the downstream policy in policy/default.yaml. The full ADR series, including the canonical JSON and audit log decisions this record depends on, is under docs/adr.

This is the format I use when a design decision has to be adopted by teams other than the one that made it. The companion notes are Standards That Survive Independent Teams and Agents Act Only Through Signed Tools.

Know an architecture team whose decisions live in chat history? Send them this page.