Skip to main content

TLDR

anyagent has three layers. Your app talks to the top one. The bottom one talks to the agent process. The middle one is where every rule lives.
The one idea to keep: adapters translate, the engine decides. An adapter turns wire frames into a small vocabulary and back. It never decides when a turn ended, whether a prompt should steer or queue, or which requests are still open. The engine does all of that once, the same way for every agent. That is why every agent behaves identically through anyagent.

Codebase map

The seam between engine and adapter

The engine and an adapter talk through two private enums. This is the whole contract an adapter has to meet.
An adapter never sends TurnStarted, TurnEnded, RequestClosed, SessionUpdated, or StatusChanged as content. The engine owns those and drops any attempt. Adapter::connect is the only other thing an adapter implements. It launches the process, does the handshake, and returns a DriverInfo: the agent’s details and capabilities, the initial configuration, the resume token, and whether this wire ends turns with its own frame.

One prompt, end to end

Three things happen in the engine that no adapter has to think about:
  1. Attribution. Frames that arrive before TurnAck belong to the previous turn and are dropped instead of leaking into the new one.
  2. Request tracking. Every RequestOpened is remembered until your answer or a cancel closes it, so NeedsInput and RequestClosed are always right. An answer the request did not offer is rejected without touching the wire.
  3. Promotion. When a turn ends and the queue is not empty, the next prompt starts immediately, and the status stays Working without flashing Idle.

How a turn ends

Not every wire says “done”. The engine handles both cases and tells you which one you got through CompletionSource. Every shipped adapter ends prompted turns with its own frame. ACP agents have no frame for agent-originated turns (the agent waking itself after background work), so those are inferred. Frames that arrive after the end and carry no new work (a late tool status, a usage update) are applied without a turn. A subagent’s end frame never ends its parent’s turn.

Native vs ACP

There are two kinds of adapter. The choice per agent is about what the wire can express. When an agent has both wires installed, discovery prefers the richer one.

Adding an agent

For an ACP agent, skip step 2: the shared adapter already speaks it. A catalog entry is only needed for verified quirks and login commands. The conformance suite in src/adapter/conformance.rs runs the engine over the mock adapter and pins the contract every adapter benefits from: one TurnEnded per turn, steer or queue, request lifetimes, cancel, close. Because the rules live in the engine, a new adapter gets them without writing any.