# Runtime

`aip-runtime` owns reusable in-process services for sessions, capabilities,
action lifecycle, event streams, idempotency, escalation, audit, settlement
state, and lease-aware queued work execution. It has no dependency on product
connectors.

## Services

| Service | Purpose |
|---|---|
| `SessionManager` | creates sessions and validates lifecycle transitions |
| `DiscoveryService` | stores manifests and capability lookup metadata |
| `IdempotencyStore` | returns cached results for replay-safe action keys |
| `EventLog` | append-only event stream with cursor reads |
| `LifecycleStore` | stores stream chunks, results, cancellations, escalations, conversations, receipt chains, audit events, and settlement batches |
| `DelegationStore` | stores parent-child delegation graph edges and terminal delegation results |
| `TransactionStore` | stores transaction and saga records, captured identity context, plans, terminal outcomes, and recoverable records |
| `ActionQueue` | stores queued/running async actions, worker leases, and terminal results for restart recovery or worker execution |

## Action Path

1. Gateway receives an `Action` envelope.
2. Runtime checks idempotency.
3. Runtime resolves the target capability from discovery.
4. Runtime authorizes the caller through `aip-auth`.
5. Runtime dispatches to a registered `ActionHandler`.
6. Runtime stores idempotent results when a key is present.
7. Gateway returns an `ActionResult`.

For `ActionMode::Async`, runtime validates the same capability, policy, and
handler preconditions, returns an `Ack` with `queued` status, and executes the
handler on the background scheduler. Terminal results are recorded in
`LifecycleStore`, settled in `ActionQueue`, and dispatched to the action
callback when one is present. Retryable failures are not recorded as terminal
lifecycle results; the runtime stores `next_attempt_at` and continues the same
action through a targeted leased retry path until it either succeeds, exhausts
the retry budget, or enters the dead-letter queue.

`RuntimeStores::durable_local` persists the async queue in `queued_actions.json`.
On startup, embedders can call `recover_queued_actions` to replay queued or
running actions through the same authorization, idempotency, handler, lifecycle,
event, and callback path. `aipd` performs this recovery during daemon startup
after registering built-in and connector handlers.

`Runtime::recover_runtime_state(RuntimeRecoveryConfig)` is the unified restart
entrypoint for protocol runtimes. It returns a `RuntimeRecoveryReport` covering
pending approvals, recoverable transaction or saga records, queued action
replay results, and running delegation recovery results. Lower-level recovery
methods remain available for embedders that need to recover one category at a
time, but production supervisors should prefer the report when they need a
single auditable recovery artifact.

For horizontally scaled workers, embedders can call
`Runtime::run_queued_action_once(worker_id, lease_ttl_ms)`. The runtime claims
one queued or expired action through `ActionQueueBackend::lease_next`, executes
the registered handler, records lifecycle state, settles or dead-letters the
queue record, emits `aip.action.worker_result`, and dispatches the callback.
Retryable failures are rescheduled before terminal lifecycle settlement, and
future retry windows are not leaseable until `next_attempt_at`.

`Runtime::run_queue_worker_until_idle(QueueWorkerConfig)` is the standard finite
worker drain for daemons and test harnesses. It repeatedly calls the same
lease-aware path, sleeps between idle polls, honors retry windows, and returns a
`QueueWorkerReport` with claimed attempts, terminal results, retry scheduling,
failed attempts, and worker errors. The method is intentionally finite so
process supervisors own shutdown, backpressure, and long-running loop policy.
When a supervisor already knows the action to resume, it can use
`Runtime::run_queued_action_by_id_once`; the claim is still atomic through
`ActionQueueBackend::lease_action`.

Backends must make `lease_next`, `renew_lease`, and `release_lease` atomic for
their storage technology. The built-in file backend is single-host durable.
`aip-storage-postgres` is the reference multi-process implementation and uses
database transactions, row-level coordination, compare-and-set transitions,
and durable queue state. Additional database or queue backends must preserve
the same lease, fencing, retry, idempotency, and recovery contracts rather than
only persisting serialized records.

## Transaction Path

Transactional actions use `Action.transaction` and, when routed as first-class
messages, `TransactionRequest` / `TransactionResult`. `DryRun` with
`SchemaOnly` or `PolicyAndSchema` fidelity returns a runtime preview without
handler execution. `DownstreamValidation` and `FullSimulation` dry-runs are sent
to the registered handler with the dry-run transaction context and are recorded
as dry-run transaction evidence without lifecycle commit. Action-level dry-runs
that omit a transaction id are normalized with a generated `TransactionId` before
preview, handler dispatch, and durable evidence persistence, so every dry-run
record remains addressable and correlation-safe.

`Plan` produces a typed `TransactionPlan` rather than opaque metadata. The plan
captures the plan id, transaction id, action and capability binding, canonical
plan-input hash, side effects, approval policy, compensation metadata, identity
snapshot, and commit requirements. `Commit` must reference a known planned
record and is rejected before handler execution if the capability, transaction
id, input hash, tenant, or external-account identity no longer matches.

`Compensate` checks that the target action completed, consults durable
transaction records when they exist, enforces the compensation window, and then
routes execution to the declared compensation capability.

## Delegation Path

`DelegationRequest` creates a durable graph edge from a parent action to a child
action. Runtime verifies that the requester matches the envelope actor, appends a
delegation chain entry to the child action, and stores the edge before any
execution starts.

Runtime then asks its configured `DelegationRouter` whether the request should be
sent to a remote peer. A router can settle the delegation by returning a
`DelegationResult`; otherwise the runtime falls back to local handler execution
as the delegate principal.

Synchronous child actions return a terminal `DelegationResult`. Asynchronous
child actions return a non-terminal `running` result, remain open in
`DelegationStore`, and are settled by the background scheduler when the child
handler completes.

The final result includes a hash-linked `delegation_made` receipt chain. Runtime
also emits `aip.delegation.requested` and `aip.delegation.completed` events so
gateways and operators can inspect multi-agent workflow progress independently
of the final result payload.

When stream chunks are recorded for a delegated child action, runtime annotates
the lifecycle event with `delegation_id` and `parent_action_id`. This lets parent
workflows and observers correlate streaming child progress without parsing
product-specific connector payloads.

On startup, embedders can call `recover_running_delegations`. Recovery replays
persisted `running` graph edges through the router first and then through local
execution. Deployments should pair recovery with child action idempotency keys
when remote peers may receive the same delegated request more than once.

## Lifecycle Path

Non-action messages are also first-class. Heartbeats, cancellations, stream
chunks, human escalation, channel messages, audit events, receipts, and
settlements are stored or converted into event stream entries. This keeps the
protocol useful for long-running multi-agent workflows instead of limiting the
runtime to request/response tool calls.
