Architecture
Agent │ ┌─────────┴─────────┐ HTTP MCP transports (carmy-http, carmy-mcp) └─────────┬─────────┘ │ ExecutionRequest Carmy Runtime policy · validation · idempotency · │ deadline · tracing ┌─────────┼─────────┐ Tool Tool Tool your code │ │ │ Service Database External APICrates
Section titled “Crates”| crate | role |
|---|---|
carmy |
the facade: carmy::app(), State, config, testing, prelude, and feature-gated transports |
carmy-core |
the domain: Tool, ToolMetadata, Effect, AgentError, AgentContext, execution types |
carmy-schema |
JSON Schema generation |
carmy-macros |
#[carmy::tool] |
carmy-runtime |
registry, policies, validation, idempotency, cancellation, event stream |
carmy-http |
discovery, catalog, execution and SSE over Axum |
carmy-mcp |
the MCP server adapter over rmcp |
carmy-observability |
tracing subscriber setup |
carmy-cli |
carmy new |
carmy-core has no dependencies on HTTP, MCP, databases or LLM providers.
The execution pipeline
Section titled “The execution pipeline”Runtime::execute(ExecutionRequest) -> ExecutionResult:
- Resolve the tool by name.
- Run the policies: authorization, confirmation, quotas.
- Validate the IDs, and validate the arguments against the input schema.
- Reserve the idempotency identity: acquire, replay, conflict, or uncertain.
- Invoke the tool under a deadline, the cancellation token and panic isolation.
- Validate the output against the output schema.
- Record the result.
- Close the tracing span.
Runtime::execute_stream runs the same pipeline and yields events.
Transports
Section titled “Transports”A transport only translates:
- its protocol into an
ExecutionRequest, with the host’s trustedAgentContext - its cancellation signal into the context’s token
- results and events into its own DTOs
Runtime types never go on the wire directly. Adding a transport requires no change to tools or to the core.
Conventions live in the facade
Section titled “Conventions live in the facade”State<T>, auto-registration (collected at link time with linkme), carmy.toml and
run() are all in the carmy crate. The core and runtime stay free of them.
Toward execution plans
Section titled “Toward execution plans”A future plan (a DAG of tool calls with $step.field references) needs no new concepts:
- Each step is an
ExecutionRequest. parallel_safedecides which steps can run concurrently.effectandidempotentdecide what can be retried or cached.- Per-step
request_ids (<plan>/<step>) make resuming a failed plan safe.
Review checklist
Section titled “Review checklist”Check these for every change:
- Is the concept part of the domain, or part of a transport?
- Does HTTP or MCP leak into the runtime or the core?
- Does application state leak into
AgentContext? - Is a new abstraction needed now, or only allowed by the architecture?
- Does it prevent execution plans or a new transport later?
- Does it make the common case harder?