Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

691 lines
45 KiB
Markdown

# Agent Host contracts
`packages/agent/host` is the provider-neutral application boundary for
canonical agent session and turn lifecycle orchestration. The package now owns
the create, resume, send, durable submit-claim, canonical title, session read,
settings, pin, delete, cancel, session fork,
interactive response, plan decision, durable runtime-operation, and complete
goal-control/reconcile application core. `tuttid` routes those commands through
`Host`; transport adapters translate their own HTTP or RPC shapes into these
provider-neutral contracts.
Tutti Mode turn snapshots use `PreferenceVersion` to separate the current
`Effect`/`Speed` pair from the deprecated single-axis
`OrchestrationIntensity`. Current writers set
`TuttiModePreferenceVersionEffectSpeed` and populate the legacy alias with the
effect value. Runtime readers treat version zero as a legacy snapshot, mapping
its intensity to effect and using balanced speed (`50`). This is an upgrade
read path, not support for connecting a new client to an older daemon.
The module owns:
- lifecycle command and runtime observation types;
- narrow canonical store, runtime, preparation, attachment, clock, scheduler,
and post-commit observer ports;
- the runtime-operation coordinator, worker, typed interactive dispositions,
and startup recovery order;
- the direct and typed goal-control saga, revision actor, durable operation and
reconcile-inbox workers, exact Goal-generation fences, provider evidence
repair, and goal recovery policy;
- the provider-neutral Session Fork saga, selected-Turn binding check, source
mutation fence, exact capability resolution, frozen canonical snapshot,
attachment staging, durable lineage, and startup recovery policy;
- the durable edit-retry saga, effective-history revision fence, authoritative
provider-history reconciliation, and explicit replacement recovery policy;
- the provider-neutral runtime-only Side lifecycle, including live-source
capability checks, idempotent open, transient execution, and cleanup;
- typed conformance scenarios under `conformance`.
`CreateSession` has three explicit modes: an empty session, one command with
`InitialContent`, or one typed `InitialGoalControl`. Initial content prepares
its submit claim before provider delivery and rolls back the provisional
canonical shell when delivery fails. Typed initial Goal is mutually exclusive
with non-empty initial content; it creates a non-provisional Session and enters
the same durable Goal saga under `ClientSubmitID` without opening a Turn.
When the provider confirms that the accepted `set` command will begin
autonomous execution, Host persists `execution_pending` on that Goal
generation. Only a canonical `goal_arm` or `goal_continuation` Turn carrying the
exact operation, revision, and repair epoch clears it; terminal/non-active Goal
observation, divergence, failure, replacement, and clear also release it. This
fact bridges loading presentation without making Goal convergence imply a Turn.
Before runtime preparation or provider startup, a retry with that identity
checks the canonical Goal operation. A completed retry returns the existing
Session and operation; an in-progress or failed operation returns its existing
state instead of starting another provider Session. This preflight is durable
across Host process restarts and does not depend on the runtime's in-memory
Session registry.
Session creation also has a Host-owned canonical initialization barrier. Every
Runtime used by `CreateSession` must implement
`RuntimeSessionInitializationPublisher`. Host calls
`Start(CanonicalInitPending=true)`, durably initializes the exact canonical
Session (including immutable rail placement), and only then calls
`PublishSessionInitialization`. While that barrier is pending, the Runtime may
start its provider connection but must buffer activity reports, stream events,
configuration updates, and command snapshots so none of them can create or
expose canonical state before Host commits it. Publication is idempotent and
releases the buffered observations in order. An initial-content Session keeps
its separate provisional submit barrier after canonical initialization and
does not become visible until its submitted intent is durable.
`RuntimeStartResult.Created` records ownership of the live Runtime for the
exact `Start` call. Failed-create compensation may close only a Runtime with
`Created=true` and may roll back only a canonical Session created by that
attempt. A retry that reuses an existing Runtime or canonical Session must not
close or delete the earlier owner's resources; it must validate the existing
immutable rail and fail closed on a mismatch. Host consumers must preserve the
`Start -> canonical initialize -> publish` sequence instead of publishing a
runtime-start report directly from `Start`.
Provider Turn acceptance is a cross-process barrier, not a generic lifecycle
notification. The runtime may move through `queued`, `dispatched`,
`provider_observed`, and `resolving_identity`, but it must not expose provider
output or interaction until the exact provider identity has been resolved. The
adapter then blocks its provider event path while the Host atomically persists
`canonicalTurnId + providerSessionId + providerTurnId`; only that commit moves
the Turn to `durably_accepted`. Streaming, waiting for approval/input, running
tools, checkpoints, and provider-root terminal events all follow the barrier
and retain the same authoritative provider Turn ID. Correlation IDs are never
provider IDs. A provider may instead fail before any provider Turn identity
exists. In that case the adapter returns an exact canonical `turn.failed`
terminal without inventing a provider ID. Runtime dispatch remains an
independent admission fact, and the runtime releases its local active-Turn
fence only after that canonical terminal crosses the synchronous durable-report
barrier. A failed or acknowledgement-lost terminal commit remains pending and
is retried idempotently; exact daemon settlement reconciliation is the only
other path allowed to release that fence.
Canonical external identity inheritance is separate from provider acceptance
and Turn lifecycle. A Turn may carry one immutable `IdentityAnchorTurnID`
pointing to an ultimate Turn in the same Session. Plan-decision completion uses
that generic relation when it confirms the implementation Turn, and commits the
anchor before the completed notice in the same canonical transaction. Host and
downstream projections consume only the relation; they never recover it from
plan text, status notices, submit IDs, or transcript page history.
The acceptance barrier does not decide whether the user's prompt is durable.
After `Exec` returns an explicit rejection or an outcome-unknown timeout, Host
records the submit provenance and lossless replay envelope on a
cancellation-independent context. An explicit rejection settles an existing
canonical Turn as failed and transitions its submit claim to terminal
`rejected`; replaying the same `ClientSubmitID` returns that failed Turn without
provider dispatch. For an initial-Session rejection, Host then closes the
startup runtime with canonical completion suppressed: the failed Session, Turn,
and prompt remain historical facts, but that runtime cannot be reactivated.
An outcome-unknown result retains the prepared submit claim for reconciliation
and never blindly redispatches. Only a new provisional Session with no visible
message or provider identity may be compensated away.
Adapters must carry the structured action/objective instead of reconstructing
it from presentation text. `ParseTypedGoalControl` remains the compatibility
path for callers that still send `/goal ...` as initial content. Resume
eligibility is decided by `ResolveResumePolicy`: root sessions resume normally,
explicit imports may recreate a missing provider session, and child,
tombstoned, or non-resumable imports are rejected. Canonical titles may be
empty; only an explicit title or the first eligible prompt establishes one.
For typed initial Goal, the display prompt (or a synthesized `/goal` command)
is the eligible prompt and is established before provider startup, even though
the Goal path does not create a Turn.
`ReprepareRuntimeSession` is the non-destructive boundary for replacing an
idle provider connection with freshly prepared MCP bindings. Host serializes
it with other Session commands, rejects both canonical and runtime active-Turn
evidence, and preserves the canonical Session, provider session ID, and
history. Its trusted `RuntimeContextOverlay` is visible only to runtime
preparation (for example to mint an Invocation-scoped bearer); it is not
persisted or installed as provider RuntimeContext. A successful reprepare must
precede the Turn whose tools use that binding.
`DisconnectWorkspaceRuntime` is the attachment-loss boundary for releasing
every live provider transport in one Workspace without deleting canonical or
Controller Session state. Host serializes each Session against ordinary
mutations, preserves the provider Session identity and history, and never
resumes a provider or replays a prompt. Provider adapters terminalize active
work and pending interactions before dropping the transport, and transport-only
disconnect must not invoke a destructive provider `session/close`. A later
user command follows the ordinary just-in-time Resume path.
Host consumers that perform a provisional runtime mutation use
`WithWorkspaceRuntimeOperation`; its callback receives the reentrant admitted
context and must own startup through cleanup. Attachment observers first call
`AcquireWorkspaceRuntimeDisconnectFence`, which closes admission immediately,
then retry `Wait` until already-admitted mutations drain. Canceling one Wait
does not reopen admission; every joined owner must call `Release`, and one
owner cannot reopen the Workspace while another disconnect remains active.
`CreateSessionInput.RailPlacement` optionally carries the caller-selected,
versioned canonical rail identity. Host validates it before provider startup
and persists its opaque `SectionKey` exactly on first creation. An idempotent
retry that supplies a placement must use the same placement; project deletion
or another adapter-side view change never reassigns an existing session to
`conversations`.
By default, a new explicit project placement must still exist in the Host's
local project registry, which fences a stale local selection after project
deletion. A trusted adapter may set
`CreateSessionInput.RailPlacementAuthoritative` when an external canonical
authority already fixed the placement. That opt-in accepts a project absent
from the local registry, but it applies only to first initialization and never
allows an existing session's immutable placement to change.
Before provider startup, Host resolves the final placement from the immutable
existing session, an explicit caller placement, or the prepared cwd through the
canonical store. It then installs the prepared cwd in `TUTTI_AGENT_CWD` and the
normalized versioned `RailPlacement` JSON in
`TUTTI_AGENT_RAIL_PLACEMENT`. Create, resume, runtime reprepare, and historical
Session Fork sources all receive that same pair. Nested callers inherit it when
they omit an explicit cwd; an explicit cwd is a new placement-selection request,
not a request to reinterpret the caller's environment. Adapters must not derive
placement from a session id, binding id, PeerCommand, or another view lookup.
Host supplies the exact canonical assignments last; the runtime process adapter
owns target-platform environment-key semantics when it materializes the child
process environment.
Cancellation exposes durable intent acceptance, provider confirmation, and
canonical settlement as separate facts. `GoalControl`, `GetGoalState`, and
`ReconcileGoal` are provider-neutral Host APIs; typed `/goal` commands enter the
same durable saga without opening a turn. `GoalControlResult.Goal` is always
the durable desired projection after persistence; provider output is retained
separately in `GoalState.Observed`. A provider may return no observation for
pause or resume without erasing the visible Goal, and only a durable tombstone
returns a nil Goal. `GoalControlResult.IntentAccepted` becomes true as soon as
that durable operation exists, even when immediate runtime readiness or
delivery returns an error; `GoalState` then distinguishes pending delivery
from terminal failure. A provider-accepted or applied Goal is also canonical
resume evidence for a turnless Goal session after the live runtime disappears.
An exact-provider cancel response can be delivery-unconfirmed: the provider
received the request but could not prove it stopped the requested Turn. Host
retains that exact durable operation for retry and canonical reconciliation; it
does not infer either `canceled` or `failed` from the response. If the canonical
Turn reaches a terminal state first, the operation completes as a no-op and
preserves that existing outcome.
If the runtime reports `provider_state_lost`, or reports an absent provider
target while the canonical Turn is still unsettled, Host performs the same
canonical-terminal check once. Without authoritative terminal evidence it
settles the target as `failed` with `execution_status_unknown`; it never
fabricates `completed`, `canceled`, or `interrupted` from provider absence.
`AdoptProviderGoal` is the narrow
reverse boundary for a Goal created by a provider tool during an already
accepted Turn. It atomically records the active provider generation as a
completed, applied operation and converged desired/observed state; it never
dispatches another provider mutation. The provider session plus immutable
generation fingerprint form its replay identity. A conflicting pending or
active durable generation is rejected, so runtime continuation remains
fail-closed instead of inheriting whichever Goal happens to be current.
Every adoption also carries the canonical Goal revision observed before its
asynchronous dispatch. Host compares that revision inside the serialized Goal
actor, so an observation queued before a newer set, clear, pause, or resume
cannot advance after the newer mutation commits. Terminal and cleared
generations may advance to a genuinely later provider-authored Goal; that
transition observes the current revision and receives a new durable revision.
A caller-stable `ClientSubmitID`
makes one goal mutation idempotent across retries and Host restarts (and takes
precedence over the legacy metadata field). `GetGoalState` is a pure canonical
read: only `GoalControl`, `AdoptProviderGoal`, `ReconcileGoal`, and recovery
workers may create or change the durable goal projection. `Recover` first
requeues and recovers
durable runtime operations, then goal operations and the goal reconcile inbox,
then settles unrecoverable stale turns. Configuring a goal store
without its runtime or inbox consumer fails recovery with
`ErrGoalConsumerUnavailable` instead of silently accumulating work.
`FenceGoalGeneration` is the durable boundary for revoking work created by one
exact Goal operation. The caller supplies the original operation identity; Host
persists `(operationId, revision, repairEpoch)` in
`workspace_agent_goal_generation_fences` before attempting any provider call.
`IntentAccepted` means Host owns later retries even when the provider is
offline. Accepting or recovering a fence never resumes an offline provider
Session. Host immediately prepares a revision-conditional local clear so the
revoked target cannot replay. During startup recovery, an absent Runtime
Session completes that clear and the fence locally without resuming a provider;
the operation remains explicit that provider application is unknown. Provider
delivery waits until the Session is already live or a user action resumes it.
A pre-crash live revocation may already have prepared its exact-Turn cancel;
startup completes that internal operation locally with an interrupted outcome
instead of retrying a missing Runtime or letting the operation shield a stale
Turn from restart settlement.
A fence is an admission rule, not session deletion: completed rows remain
durable and are loaded into Runtime resume before the Session can accept Goal
or Turn work, so a delayed provider generation cannot become canonical. The
runtime Controller retains the exact fence set independently of an adapter
connection and installs it into every replacement connection before dispatching
a user operation; failed installation closes the unprotected connection. Host
cancels a live Turn only when its
immutable Goal provenance exactly matches the fenced identity; that internal
cancel is require-live and never reconnects an offline provider. The fence
remains unsettled until the Turn reaches an authoritative terminal state. It
never retargets
`session.activeTurnId`, clears a newer Goal revision, or infers ownership from
the current session state. `FindGoalControlOperationByClientSubmitID`
lets an adapter recover the original operation after an
accept-before-response crash without duplicating Host's operation-ID
algorithm. Startup and steady-state workers process fences before ordinary
Goal operations; otherwise a prepared revoked Goal could be replayed during
recovery before its fence reached the runtime.
`GetGoalActivityTurn` is the read-only projection proof for consumers that
observe a turnless Goal Session. A candidate must be the latest active Turn,
have a Goal-owned origin, and carry an exact operation/revision/repair-epoch
identity backed by the durable Goal operation store. Consumers may use the
returned Session and Turn to authorize live projection; they must not infer
Goal ownership from `Session.ActiveTurnID`, Turn recency, or origin alone.
> **Currently disabled.** Durable edit-and-retry is neutralized in production via
> `Config.EditRetryDisabled`: its saga can strand a session in a rolled-back-but-
> not-resent state whose runtime operation becomes a cold-recovery poison pill
> that crashes `tuttid` on launch. While disabled, `GetEditRetryAvailability`
> reports unsupported, `EditRetry`/`RecoverEditRetry` refuse, and recovery
> quarantines any leftover operation (failing it and clearing the session's
> history fence back to `ready`). Re-enable only once the resend/recovery gap is
> fixed. See the troubleshooting entry "A stuck edit-and-retry operation crashes
> the daemon on every launch". The behavior below describes the feature when
> enabled.
A completed latest user Turn may be edited and retried only through
`GetEditRetryAvailability`, `EditRetry`, and `RecoverEditRetry`. Host owns the
complete lifecycle: it snapshots the lossless submitted content, serializes the
Session mutation, checkpoints before provider rollback, retracts exactly one
effective Turn only after authoritative confirmation, then submits a stable
replacement Turn. The replacement keeps attachments, mentions, capability
references, and the Tutti mode snapshot while replacing only the first text
block. Retraction changes model-visible history and canonical projections; it
does not compensate filesystem changes produced by the original Turn.
Conversation timelines hide retracted Turns, while audit reads and generated
file projections retain their submitted content and filesystem side effects.
`operationId`, replacement `turnId`, and `clientSubmitId` remain stable across
retries and process restarts. A direct provider acceptance receipt completes
the operation without polling. If a rollback or replacement response is lost,
Host reads the provider's effective history and fails closed on identity
divergence. An uncertain rollback is never dispatched twice. An uncertain
replacement is never resent until an explicit `retry_replacement` command has
proved its absence from authoritative history; a read-only `reconcile` command
cannot dispatch provider work. While history is fenced in
`rollback_pending`, `resend_pending`, or `recovery_required`, ordinary sends
are rejected rather than appended to an uncertain model context.
The authoritative absence proof is consumed in the same SQLite transaction
that removes a discardable failed replacement placeholder and advances the
stable replacement attempt. There is no separately committed redispatch
authorization checkpoint. Replaying the same proof is idempotent; a later
authoritative proof advances a new attempt only after the previous failed
placeholder and claim are safely discarded.
A provider-accepted Goal operation has crossed the delivery boundary. The
steady-state worker waits for applied evidence and never resubmits that
mutation; the accepted convergence deadline terminates a lost-evidence case.
Startup recovery may replay an accepted mutation only according to the
adapter's recovery policy. In particular, a query-incapable adapter may replay
an idempotent clear once to resolve a crash window, while unsafe set replay
remains rejected.
`GetSession` reads canonical session truth plus an optional live runtime
observation without starting a provider. `GetSessionWithRailPlacement` adds
the Host-owned immutable rail proof for idempotent recovery; application
adapters must not reproduce rail normalization from canonical fields.
`GetTurn`, `GetInteraction`,
`ListSessionTurns`, `ListSessionMessages`, `FindTurnByClientSubmitID`, and
`GetSessionInteractionSnapshot` expose canonical queries without leaking an
adapter's concrete store. `GetSessionInteractionTreeSnapshot` is the
execution-tree read boundary: it accepts only a root Session, resolves an
optional latest root Turn in the same read transaction, and returns that root
Turn plus every descendant Session's latest-Turn interactions. Deleted
Sessions and retracted latest Turns are excluded, and the full result is
ordered by Session, Turn, and request identity. Turn pages are newest-first,
bounded metadata reads with stable cursors. Message pages use per-session
version cursors and may be narrowed to one turn. `GetInteraction` requires the complete
`(workspaceId, agentSessionId, turnId, requestId)` identity. The interaction
snapshot contains every interaction on the latest turn and derives its pending
subset from that same read; older-turn pending rows can never become current
actionable state. Canonical transaction participation derives one deduplicated
`interaction_tree/dirty` fact for every affected execution tree. Interaction
changes, Turn creation/settlement/retraction, and Session deletion are all
covered; the fact carries the immutable root Session and root Turn identity so
consumers wake and reread one tree without reconstructing lineage. A root
Session deletion uses an empty root Turn as an explicit all-turns wake. These
facts are invalidation hints, not partial row updates. Consumers publish the
reread result as one complete `interaction_snapshot` carrying the returned
exact root Turn; an empty interactions array is an authoritative clear only for
that explicit root. Collection contents or authorization-list order must never
be used to infer the current root. `CreateSessionInput.ClientSubmitID` and
`SendInput.ClientSubmitID` are the typed idempotency identities and override
the legacy metadata value when both are present. The matching durable submit
claim's immutable `CreatedAtUnixMS` is the canonical occurrence of that user
message. Host passes it to both runtime execution and durable submit-provenance
reporting; adapters must derive the same message sequence from that occurrence
regardless of which report reaches storage first. `ClientSubmitID` identifies
the submission but is not itself an ordering value.
Guidance is a mutation of an existing canonical Turn, not a request to steer
whatever happens to be current when transport completes. `SendInput.Guidance`
therefore requires an explicit `TurnID` at the Host boundary. Host and the
runtime Controller compare that identity with the live active Turn under the
session lifecycle lock before provider admission. A mismatch returns a typed
`NotDispatched` result, makes zero provider calls, and removes a prepared
submit claim; callers must surface the rejection or retry with a newly captured
target rather than silently redirecting the guidance.
Accepted guidance follows the provider's native active-turn semantics while
keeping the canonical Turn active. A soft-steering adapter may insert guidance
into the current provider response without interrupting it. A preemptive
adapter must close the interrupted response's live message/tool projections
and publish its provider-turn terminal boundary before admitting guided output.
Neither form is a canonical Turn cancel or a second user Turn.
Interactive responses follow the same ownership rule. Runtime may return a
provider-neutral follow-up intent after an interactive denial, but it does not
dispatch that prompt itself. Host checkpoints the intent on the leased
interactive operation, waits for the answered Turn to become idle, and submits
the prompt through `SendInput` with the stable id
`interactive-deny:<operation-id>`. The checkpoint also persists the terminal
interactive disposition, so recovery does not depend on Controller memory or
an existing Runtime Session. Recovery reuses that disposition and id; if the
provider connection is temporarily absent, the operation remains retryable
until ordinary Host admission can replay the prompt without creating a
duplicate Turn.
Accepted runtime Session reports reconcile their Goal snapshot through the
canonical bottom-up observation path without overwriting a newer desired
intent. When that changes the public Goal projection, the same transaction
emits a `goal_state` mutation; timestamp- or evidence-only refreshes remain
silent. A matching applied observation may also complete the pending Goal
operation in that transaction. Consumers use these post-commit facts only as
wake hints and reread canonical Goal state.
For a user Turn, runtime acceptance is not complete until the provider returns
its exact Turn identity and the activity reporter durably installs
`canonicalTurnId -> providerSessionId + providerTurnId`. The direct acceptance
path is synchronous with the Host command while subsequent provider output
remains asynchronous. A local persistence failure after provider acceptance is
reported as delivery-unknown and retains the submit claim; it must never cause
an automatic redispatch. Providers receive only the opaque `ClientSubmitID` as
a correlation identity. Canonical Turn ids remain Tutti-owned and are not
projected into provider client-identity fields.
`CaptureHistoricalSessionGraph` and `RestoreHistoricalSessionGraph` are the
provider-neutral Replay boundary for settled Session, Turn, Message,
Interaction, Goal, hierarchy, stable settings state, and the narrow portable
`providerResumeCheckpoint` required to resume an already-initialized protocol
boundary. The checkpoint is opaque to Host; full provider runtime context is
not exported. Restore is for a fresh isolated Workspace before normal Host
recovery. `HistoricalSessionGraphRestoreInput` carries the target Workspace and
required current User outside the portable graph; restore binds every imported Session
to that runtime-owned identity without serializing it into a Cassette. It is
idempotent only for identical content and the same target User, rejects graph
or ownership conflicts, and never starts or resumes a Provider.
Runtime adapters preserve explicit downstream failures as `ProviderError` so
Host consumers can distinguish provider-owned rejection from preparation,
canonical-store, timeout, and other local failures with `errors.As`. The
provider code and diagnostic text remain local observations rather than a
stable cross-service taxonomy; coordination layers persist only their own
coarse product reason when needed. `NewProviderError` deliberately leaves
cancellation and deadline failures unclassified because their delivery result
is unknown and must remain recoverable. The narrow
`NewProviderStartTimeoutError` exception is used only after the runtime owner
has observed the provider adapter's Start stage time out before establishing a
runtime Session. The daemon keeps the existing `request_timed_out` AppError
code for API and presentation behavior and carries that narrow verdict as
`ErrProviderStartTimeout` in the error chain. The Host runtime adapter maps the
marker to `provider_start_timeout` while preserving the deadline cause; callers
must not infer that verdict from an arbitrary context deadline.
`UpdateSettings` serializes with runtime resume:
historical sessions persist settings only, while live sessions update the
runtime first and persist the resulting settings only after the runtime
accepts the change. Provider-specific model, reasoning, and speed normalization
stays behind `SettingsPolicy`. A model change invalidates the previous model's
context-window usage in both the live observation and canonical metadata;
provider quotas remain valid and are preserved. `UpdatePin` mutates canonical
metadata only.
`DeleteSession` and `DeleteSessions` enter one deletion coordinator. The
canonical store first resolves the complete root/child closure; Host acquires
the shared session-mutation actor and session locks in stable order, closes
every live runtime in that closure, and commits only if the store resolves the
same closure inside the write transaction. An optional provider-neutral
`SessionDeletionGuard` receives that exact Host-owned closure before any
runtime close or canonical delete. A rejected admission returns with no
canonical lifecycle side effects. Every admitted attempt is reported as
completed or failed; reporting is observational and cannot change the command
result. A changed child tree is reported as a failed attempt, replanned, and
re-admitted with its new exact closure before Host closes any newly discovered
runtime or writes a tombstone. A requested runtime that is live before its
first canonical report is still closed and cleaned up by the same coordinator;
the empty canonical plan simply skips the tombstone transaction, so deleting an
already absent session is a successful no-op and batch responses retain empty
arrays for their ID fields. Goal provider mutations use the same outer
session-mutation actor, so clear/set/reconcile work
cannot race session deletion. Post-commit runtime cleanup failures are reported
separately from the committed delete result. Authorization, shared bindings,
transport DTOs,
and local view cleanup remain adapter responsibilities.
`PurgeDeletedSessions` is the separate permanent-removal command for bounded
batches of canonical tombstones. Host owns the command boundary and delegates
the atomic hard delete to its narrow `SessionPurgeStore`; retention timing,
daemon-idle scheduling, HTTP exposure, and optional compaction stay in the host
adapter. When durable adapter-owned filesystem cleanup is required, its intent
must commit in the same product transaction as the canonical hard delete; the
adapter performs that work after commit and retries failures. Host neither
chooses those paths nor treats cleanup failure as a canonical purge failure.
Each candidate is
fenced by the exact persisted `deleted_at` value, so a concurrently restored or
recreated row is preserved.
Candidate selection groups each topmost tombstone with its complete descendant
tree. A tree containing a live or too-new member is retained, while an eligible
tree is removed in one transaction even when its row or payload count exceeds a
normal batch bound; unrelated eligible trees cannot be starved by blocked
ancestors. Purge results expose only content-free session descriptors and
aggregate message/payload counts. The shared conformance scenario verifies
live and too-new preservation, exact-cutoff removal, complete-tree atomicity,
and idempotent replay through Host.
Recoverable deletion is a separate lossless tombstone lifecycle. New deletes
preserve every selected root/child Session component, its Turns, Messages,
Interactions, effective-history records, provider resume identity, and
attachment references; only live work is terminalized. Batch deletion computes
component size from connectivity inside that exact delete set, so sibling child
subtrees under one canonical root remain independently restorable. If a later
delete selects a live ancestor, complete current-version descendant tombstones
are absorbed into one new component generation without settling their work a
second time. Legacy or incomplete descendants are never upgraded: the new
topmost component stays unavailable for restore but remains permanently
deletable.
Stable Goal and effective-history state is left byte-for-byte unchanged. A
pending Goal operation is failed and detached from the state, while its desired
Goal, observed Goal, revision, and tombstone semantics remain available after
restore.
`ListDeletedSessions` exposes workspace-scoped topmost tombstones—those with no
tombstoned parent—with stable `updatedAt + sessionId` paging and explicitly
marks legacy lossy tombstones unavailable. Its summaries, project-option
catalog, and optional filter use the exact persisted `railSectionKey` as their
identity; the retained project path is presentation metadata only.
`RestoreDeletedSession` restores the
exact component atomically without starting or resuming a provider.
`PurgeDeletedSessionTrees` permanently removes selected topmost components, or
all such components in one Workspace. The optional
`DeletedSessionLifecycleScenarios` conformance catalog verifies that
delete-to-restore boundary independently from retention maintenance.
`ForkSession` creates an independent root Session through an inclusive
canonical `SessionForkPoint`. The provider driver must attest native
`throughTurn` support and the selected canonical Turn must be settled. A
non-empty provider Turn id is not sufficient: the owning Agent must also have
written opaque `provider_turn_binding_json`, and its `CanForkProviderTurn` hook
must accept that exact id/JSON pair. Session-detail projection and Fork
execution call the same hook. Rows created before the JSON binding existed
intentionally remain unbound. Descendants, active work on other Turns, and
pending Interactions are not eligibility inputs.
If an otherwise eligible historical Turn is missing only that binding,
`ForkSession` performs one read-only provider-history repair before repeating
the canonical boundary check. The primary proof is the durable submit claim's
opaque correlation identity. Truly old Claude text-only submissions may use a
per-request HMAC equality proof over one complete text block; multimodal,
attachment-bearing, context-enriched, incomplete, duplicated, and ambiguous
history fails closed. Codex has no legacy text recovery because its stable
`thread/read` shape does not expose an equally authoritative complete prompt.
No provider Turn is ever selected by index. The SQLite repair is an idempotent
empty-binding compare-and-swap and rejects provider Turn identities already
owned by another canonical Turn. Recovery and provider-owned Fork results pass
through the owning Agent's binding writer. Claude stores its checkpoint inside
its private JSON payload; Codex and Tutti Agent `thread/fork(lastTurnId)` use
the shared app-server payload schema and provider Turn id while retaining
separate runtime version attestations and isolated provider homes.
Target titles use one lineage-family sequence (`Title (2)`, `Title (3)`, ...)
rather than restarting the suffix when a child Session becomes the next source.
Every fail-closed boundary rejection retains a stable, content-free reason
through Host. HTTP adapters may project it as structured diagnostic metadata
while preserving their existing coarse conflict reason; transcript payloads
and attachment contents never enter that reason.
Session Fork is exposed directly when the provider/runtime attestation and the
selected canonical Turn satisfy the capability boundary. Product adapters do
not add a separate feature-preference gate; execution still revalidates the
exact provider and Turn facts before dispatch.
Capability projection is preparation-free. It reads either the live runtime
observation or the persisted runtime/driver attestation and never resolves
credentials, prepares a target context, or starts a provider process merely to
render a Fork action. `ForkSession` performs the complete preparation and
revalidates the exact driver before dispatch.
Prepare freezes the complete canonical snapshot through the selected Turn,
allocates all target canonical identities, and stages only the attachments
referenced by that snapshot. Source reporting and Goal/runtime/submit activity
continue against the live source; only physical deletion is retained while the
operation may still need frozen resources. Multiple explicit Forks from the
same boundary are valid. Host eligibility remains independent of source
activity. The shared GUI exposes Fork only on settled Turns whose provider
binding is durably `bound`, including earlier settled Turns while newer work is
active. A `recovery_required` Turn must be repaired and reprojected as `bound`
before the action is exposed; the Fork action is not a binding-repair control.
The GUI disables only the exact Turn whose own Fork request is currently in
flight.
Fork uses the durable
`prepared -> dispatching -> provider_accepted -> committed` saga. Provider
dispatch happens only after `dispatching` commits. The selected provider Turn
must exist in the provider source; earlier provider history is trusted and is
not compared with Tutti's canonical prefix. Provider acceptance, including the
child provider Session id, is persisted before any host-copy binding or
canonical materialization. A `provider_accepted` retry therefore performs only
idempotent local binding and commit and never invokes the provider again.
If provider-owned acceptance evidence cannot cover every provider-bound Turn
in the frozen canonical snapshot, Host treats that evidence as permanently
inconsistent rather than inventing provider identities. It atomically marks
the operation `failed`, preserves the provider acceptance evidence and source
history, removes any incomplete target, and releases the target reservation
and source-boundary barrier so startup and later operations can continue.
Failure to persist that quarantine remains a startup recovery error; transient
SQLite failures are never reclassified as permanent inconsistency.
`prepared` is safe to continue during startup recovery because provider
dispatch has not begun. A crash in `dispatching` becomes `unknown` and is never
automatically redispatched. There is no deterministic-replay compatibility
path. Public status retains `accepted / committed / failed / unknown`, while
the operation phase exposes `frozen / dispatching / materializing / committed /
failed / deliveryUnknown`.
Canonical commit consumes the frozen snapshot rather than re-reading the live
source. It remaps Session, Turn, Message, Interaction, and attachment ids,
persists immutable lineage, normalizes a nonterminal boundary to
`settled/interrupted`, and changes copied pending Interactions to `superseded`.
Provider-owned mode records the returned selected-Turn mapping; host-copy mode
first makes the accepted provider child independently readable. The target cwd,
settings, and runtime context come from the same prepared runtime observation
used for provider dispatch.
Interactive responses establish their winner at the canonical interaction
transition, not in a GUI or CLI adapter. Preparing an interactive runtime
operation atomically moves the interaction from `pending` to `answered` with
the requested action, option, and payload. A competing response compares its
request with that durable output: an identical response is `answered`, while a
different response is `superseded`; neither path leaks operation-conflict or
in-progress errors to the responder. The Interaction's pre-delivery `answered`
state is a durable claim marker, not the runtime's terminal result; completed
operation and responder dispositions follow an authoritative runtime
`superseded` result instead of being overwritten by that marker.
Interactive identity is always the typed `InteractionRef` tuple
`(workspaceId, agentSessionId, turnId, requestId)`. Provider request ids remain
unchanged and are only unique within their owning Turn. The response payload
contains no identity fields. Durable operation idempotency uses the same tuple;
an operation id that disagrees with its structured identity is an invariant
failure and must fail closed rather than guessing or rewriting stored data.
Adapters retain authorization and identity, transport, runtime process or VM
selection, desktop APIs, attachment ingress, and cloud inbox/outbox behavior.
Adapter-only create fields such as transcript source paths and materialized
skill bundles intentionally remain outside the Host contract.
`tuttid` production wiring resolves canonical/runtime ports and grouped adapter
dependencies before constructing the agent service. It creates shared narrow
components, uses their `HostSupportPorts` with canonical/runtime ports to
compose one long-lived `Host`, then passes that completed Host and the same
components to `NewService`. Production never mutates Service fields or calls a
post-construction Host setter. Support adapters retain only their narrow
component dependencies, never the complete Service facade. Runtime preparation
may read Session Fork lineage and its operation through a committed-only seam:
the lineage is created atomically with the committed fork, and the seam performs
no canonical write or lifecycle reconciliation. That reader is a required
canonical composition port rather than an optional runtime assertion, so
provider-state binding cannot silently omit committed Fork identity
verification.
Startup invokes `Host.Recover` before serving traffic and starts the Host-owned
runtime and goal workers. Adapters can use the supervised
`Host.Run` entrypoint to start the runtime-operation, goal-operation, and goal
reconcile-inbox workers as one lifecycle; an
infrastructure-level worker exit cancels its siblings, while retryable item
failures remain worker-local. The
individual worker entrypoints remain available for existing focused wiring and
tests. The service package translates
HTTP/query/composer/analytics concerns and provider-specific preparation only;
session, turn, runtime-operation, and goal lifecycle decisions remain in Host.
Isolated service tests may lazily compose the same adapter set, but production
startup never creates a Host per request or per session.
Canonical commits have two distinct extension points. A store-sqlite
`TransactionParticipant` may append a caller-owned durable marker inside the
same transaction as runtime/goal intent and canonical facts; it receives a
narrow transaction writer rather than `*sql.Tx`. After commit, Host emits a
typed `CommittedDelta` to `CommitObserver` for view invalidation, event-stream
wakeups, analytics, and worker scheduling. Observer failure never rolls back or
changes the command result. Work that must survive observer failure must first
be represented by the transaction participant's durable marker; legacy
workspace-only change notifiers are optional latency optimizations.
`TerminalFailureObserver` is the aggregated failure-only seam. A failed command
or a Turn whose canonical outcome is `failed` may produce one observation;
`completed`, `canceled`, and startup-reconciled `interrupted` Turns do not.
Consumers that need the exhaustive terminal population must read
`CommittedDelta.RootTurnsSettled` from `CommitObserver` and classify the
canonical outcome instead of inferring cancellation from a failure callback.
Re-derivable adapter projections are deliberately outside the participant
contract. Adapters repair those while consuming canonical state rather than
coupling their schema to every Host transaction.
Canonical deletion tombstones are not re-derivable after hard deletion, so
session delete, batch clear, and failed-create compensation also participate
before commit.
The conformance harness depends only on the public Host contract. An
implementation supplies a `conformance.Driver`, seeds its own canonical and
runtime fakes in `Reset`, and runs every value returned by
`conformance.Scenarios`. This lets `tuttid`, the extracted Host, and downstream
adapters share one behavior baseline without importing one another.
Coordinator, goal, and commit-observer scenario groups extend the same driver
with recovery ordering, recovery failure propagation, post-commit failure
semantics, and exact-tombstone permanent removal semantics.
Deletion-admission scenarios are required members of both the standard adapter
and application-core catalogs; the focused deletion-admission catalog reuses
those same scenario values rather than defining a second behavior suite.
The conformance package keeps its shared fixture and driver contract in
`conformance.go`, explicit scenario membership in `scenarios.go`, and scenario
runners in capability-named files. A scenario shared by multiple catalogs must
reuse the same package-level scenario value; catalog ownership must not be
inferred by matching its display name.
The Host release module depends on `store-sqlite` and
`store-sqlite/canonical`, but not on `daemon`, sidecars, or `tuttid`. Canonical
activity snapshots, report observer types, provider identities, capability
vocabulary, and plan-decision strategy live in `store-sqlite/canonical`.
Daemon packages retain source-compatible aliases for existing consumers;
runtime mechanics remain daemon-owned. Title normalization and initial-title
CAS derivation are Host application behavior rather than canonical vocabulary.