Skip to main content
Connect an agent safely | Entity RuntimeExpose named lifecycle operations to an agent without giving the model authority over canonical state or provenance.Entity Runtimehow-toentity-runtimehow-toadopterdeveloperreference

Connect an agent safely

The safe integration boundary is intentionally small: the agent proposes an operation and domain arguments; trusted code decides everything about authority and durable state.

Divide the inputs by trust

Agent may proposeTrusted shell supplies
operation name from an allowlistvalidated definition and version
evidence-derived reasoncanonical current instance
domain arguments explicitly delegated to itentity identity and expected revision
a request to escalateauthenticated actor and authority role
record ID, timestamp, correlation, causation

Do not expose a tool that accepts an arbitrary instance, definition, actor, or state patch. If a policy needs the actor's role, inject that value into the operation arguments after authentication.

{
"name": "operate_refund",
"input": {
"id": "refund-104",
"operation": "approve",
"arguments": {
"reason": "customer supplied delivery evidence"
}
}
}

The tool handler should:

  1. allow only the definitions and operations intended for this agent;
  2. load the instance from the authoritative store by id;
  3. preserve the revision the proposal was based on and inject trusted values such as actor_role;
  4. call StoredRuntime::execute with that expected revision, or use the equivalent kernel/provider sequence;
  5. return typed refusal JSON unchanged when the request is refused;
  6. return success only after the recorded decision commits at the expected revision; and
  7. trigger downstream work only after the commit succeeds.

StoredRuntime performs the load, retry check, decision, and recorded commit. Do not commit its result a second time. Generic entity execute --store reloads current state and has different retry semantics. The schema-derived MCP server exposes recording and domain fields directly; an untrusted agent needs a wrapper that derives or validates authority fields before dispatch. Merely supplying a trusted-looking sample value is not authentication.

Handle outcomes by kind

  • invalid_transition: refresh state and reconsider the plan.
  • validation: repair the named argument paths.
  • precondition_failed: the observed facts contradict policy; choose another operation or escalate.
  • precondition_unobservable: gather the named missing facts before retrying.
  • StoreError::RevisionConflict: another writer won; reload before proposing anything else.
  • StoreError::Unreachable: do not treat the entity as absent and do not invent replacement state.

Never parse the human sentence on stderr. The CLI writes kernel refusals with a kind; a File Store refusal is { "refused": true, "by": "store", "detail": "..." }. Both use exit 1 for a valid request that policy or storage refused. Embedded Rust callers match CoreError and StoreError variants directly.

Install the CLI skill

The installed command can render a compact Agent Skills document for its exact version:

entity skill --out .agents/skills/entity/SKILL.md

The file teaches safe input forms, exit meanings, recording metadata, and File Store migration. The stdout and file forms are byte-identical. An existing path is left untouched unless that exact replacement is authorized:

entity skill --out .agents/skills/entity/SKILL.md --force

The skill teaches command use; it grants no authority. Repository instructions, tool allowlists, authentication, and trusted input injection still define what the agent may do.

For model and harness evaluation, entity mcp projects the same validated argument schemas into tools such as refund.submit and refund.approve. Mutating calls require recording metadata and the revision the model observed, so stale intent is measurable rather than silently applied to newer state.

Test the boundary, not only the happy path

For every operation exposed to an agent, test:

  • a legal transition;
  • the same operation from an illegal state;
  • invalid and missing arguments;
  • every policy refusal;
  • missing evidence and all reported unresolved paths;
  • a concurrent revision conflict;
  • retrying the same record ID with identical and different bytes; and
  • that no refusal changed state or emitted an event.

See Typed refusals for the complete outcome reference and The Rust libraries for an embedded shell.