Architecture
Governing boundary
For compiled code, the Spice bean graph is the extension graph. Constructors
receive exact typed dependencies; ordered slices and named maps are generated by
Spice; primary, fallback, qualifiers, lifecycle, modules, and test overrides use
Spice directly. agent.Engine executes an already-constructed graph and is not a
container.
The kernel owns provider-neutral values, one deterministic single-agent loop, sequenced events, cancellation, panic containment, and snapshots. It does not own providers, filesystem or shell tools, persistence, permissions, telemetry, MCP, Git, indexing, TUI code, subagents, or distributed scheduling.
New and resumed execution use a transactional kernel boundary. PrepareStart
allocates the stable run ID and acquires the current immutable plan; snapshot
preparation acquires the exact recorded generation. Both validate and build an
unregistered event log without publishing events or starting execution. An
outer host can therefore acquire authority before Commit atomically registers
the run and starts it with a separately supplied caller-owned root context.
Abort/Close release the uncommitted log and lease exactly once. Preparation
contexts never become run lifetimes, and no protocol or daemon authority policy
is embedded in the kernel.
Runtime plugins are the sole dynamic graph. A run leases one immutable plugin
generation; activation never changes existing runs and never alters compiled DI.
The kernel expresses this boundary generically as stage.ToolPlanSource and
stage.ToolPlanLease; it has no plugin process or protocol knowledge. Existing
embedded constructors use StaticToolPlanSource, preserving direct-call Go.
Ordered dispatcher decorators are applied once to a merged dispatcher and must
preserve its snapshotted definition set. A leased dispatcher rejects any call
not present in that snapshot.
annotation/agent exposes @Stage, @Tool, and @ModelProvider. Their typed
handlers return only Spice’s generic provider and bean-metadata contributions.
Factory signatures remain ordinary Go and the generic compiler owns exact type
identity and aliases. It transports canonical per-result facts; handlers
validate their public interface contract and standard cleanup/error forms
without parsing display strings. The authorized
cmd/spice-agent-annotations process is protocol dispatch only; it is not an
application registry. Typed stages use distinct stage.Stage[Input, Output]
interfaces.
internal/compositionfixture is the executable architecture proof. Its
committed internal/spicegen/compositionproof target constructs exact aliased
model, tool, and generic stage interfaces through direct calls. The graph
demonstrates fallback suppression, typed primary selection, alias-qualified
injection, ordered slices, canonical named maps, reverse cleanup, rollback, and
typed application-owned test overrides. Negative testdata graphs prove
deterministic ambiguity and fail-closed concrete-to-interface injection.
Dependency direction
message tool interaction \ | / model | stage | event <- agentPublic contracts do not import repository internals. Protocol and host packages may depend on public contracts; the kernel does not depend on transport, UI, or provider implementations.
The public daemon package is the transport-independent host seam. A generated
DefinitionSet wraps exact immutable agent.Definition values and owns the
server-side model/turn policy. Stable cryptographic client identities use
compare-and-swap ownership epochs derived from the daemon root context. A
bounded per-client idempotency ledger commits canonical success, business
failure, or secret-safe uncertain outcomes. PendingHub is both the kernel
interaction broker and the stable-client-partitioned discovery source. Explicit
run bindings route accepted prompts to one client; each subscription atomically
captures that client’s sorted complete snapshot and gap-free revisioned tail.
Reconnect fencing joins old observer delivery before returning. Global and
per-client run, prompt, observer, and queue budgets bound retained state, and
shutdown joins delivery while releasing every binding. These primitives contain
no gRPC, Protobuf translation, listener, endpoint, or OS IPC behavior.
Every client epoch also owns a bounded commit and stream gate. Mutating work crosses an exclusive FIFO commit boundary; a reconnect intent takes priority, waits for the active commit, cancels old stream contexts, and cannot publish the next epoch until every old sender has joined and released its stream lease. Queued mutation, reconnect, and stream-acquisition claimants are hard-bounded and included in shutdown drain accounting. Known stale owners receive exact expected/observed epochs, while an unknown identity exposes no invented facts.
The public client package is the matching transport-independent consumer
seam. It contains immutable negotiated connection, run, event, interaction,
snapshot, health, and typed recovery values plus concurrent Connector,
Session, and stream interfaces. It deliberately imports only the standard
library. Successful replay/tail controls are explicit frames, arbitrary
interaction JSON is bounded and secret-redacted by default, and every typed
failure preserves the wire status’s common safe facts. Authentication,
discovery, gRPC translation, and OS endpoint ownership belong to an adapter,
not this contract or the TUI.
common/v1 and engine/v1 are the only initial Protobuf process boundary.
They encode protocol negotiation, typed status, server-owned definitions,
stable-owner reconnect, health, atomic run/event replay, complete-first pending
interaction streams, cancellation, suspend/resume, and snapshot transfer.
Authentication remains transport metadata. Handwritten validators fail
closed before state mutation. The packages deliberately contain no listener,
daemon, client lifecycle, registry, or translation into kernel internals. The
quality gate rejects gRPC, Protobuf, and generated protocol imports from kernel
packages.
Snapshot transfer is available only on protocol minor 1 with the negotiated
snapshot-authority-v1 capability. Every envelope carries a 32-byte authority
scope, positive key generation, and HMAC-SHA256 over canonical semantic fields.
Construction requires a trusted signer and import requires a keyed verifier;
the unkeyed validator checks structure and payload integrity only. Authority
keys are not IPC authentication tokens and remain outside Protobuf, snapshots,
events, logs, and errors.
daemon.RunAuthority implements that signer/verifier seam with a distinct
current-user persistent key and scope, signed lifecycle records, and stable
per-run OS locks. It retains the validated authority-directory object for its
whole lifetime: Unix child operations are descriptor-relative and Windows
child operations are handle-relative. Store shutdown rejects new leases and
drains existing run/import leases before closing the bound directory. Import
is deliberately transactional—verify, persist IMPORTING, commit the prepared
kernel run, then persist ACTIVE—and any ambiguous persistence attempt makes
that transaction terminally uncertain until it releases its lock.
Local resume has the inverse publication order needed to prevent snapshot
replay without releasing execution early: Run.PrepareLocalResume reserves the
exact suspended boundary, authority Resume durably invalidates its signed
snapshot, and only then does the host commit the kernel reservation. Aborting
the reservation before the authority transition restores the byte-identical
suspended snapshot. Cancellation and engine shutdown latch behind the pending
decision and cannot start post-boundary work.
Every interaction broker call carries an immutable validated run scope. Prompt
content and response values stay out of authoritative run events; process
clients discover them through an atomic complete pending snapshot followed by
revisioned opened/closed deltas. The event log exposes count-and-byte-bounded
pages whose bounds and optional final-head tail registration are captured under
one lock, including empty initial [1,0] and imported [N+1,N] tails.
Every tool definition explicitly classifies external-state effect and replay
safety and carries a canonically ordered capability set. Model-visible tool
problems are normal correlated results and may continue the model loop;
infrastructure failures are bounded correlated tool.ExecutionError values.
An uncertain mutating outcome is distinct from a definitive failure and can
never authorize automatic replay. These contracts are kernel-owned and apply
equally to compiled and future runtime-plugin tools.
The engine acquires a tool plan only after public input validation and before ID allocation, event-log mutation, or execution. It releases the lease exactly once on every rollback and terminal path. Release precedes run-terminal persistence, making cleanup failure part of the single authoritative terminal state. Cancellation classification comes from the run/request context, not an arbitrary error cause returned by an extension.
Generated engines provide one semantic SnapshotCompatibilityIdentity and
compiled identities for every executable provider, stage, observer, broker,
static tool, and dispatcher decorator bean. Convenience constructors leave the
compatibility identity empty: runs, local suspend/resume, and snapshot
inspection work, but cross-engine ResumeSnapshot fails closed. A plan source
is trusted to keep leased behavior immutable and never reuse a PlanID; the
kernel can freeze definitions and guard names, but cannot clone Go behavior.
See docs/adr/0001-spice-native-composition.md for the rejected parallel graph.