fix inbox lifecycle downstream contracts

This commit is contained in:
_Kerman
2026-07-31 22:00:39 +08:00
parent 8e88b17c9f
commit afedf18ccf
219 changed files with 5660 additions and 4556 deletions
@@ -18,11 +18,11 @@ The durable phases are `active`, `paused`, `blocked`, and `complete`. A blocked
### Durable record and replay
Every non-clear mutation uses `Agent.inject()` to append a model-visible `context/message` containing a versioned full snapshot; the session projects that content verbatim. Clear appends a revisioned tombstone. The context source is `{ kind: 'goal', goalId, revision, round: 0 }`; metadata and rendered `<goal_state>...</goal_state>` content must agree exactly. This descriptive delimiter follows the repository's existing `<workspace_context>` convention and [Anthropic's published guidance to structure mixed prompt content with consistent descriptive XML tags](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#structure-prompts-with-xml-tags). That is public model-experience prior art, not evidence about any provider's proprietary training corpus. The session log is the only durable source of truth, so persistence and fork inherit goal records without another database or header field.
Every mutation uses `Agent.inject()` to insert one round-zero goal-sourced message containing a versioned full snapshot or, for clear, a revisioned tombstone. The mutation commits in the durable `agent/inbox/spliced` insertion even if its context remains queued and never reaches the model; later removal or discard does not roll it back. If the same message is admitted as `user/message`, replay verifies its id, source, metadata, and rendered `<goal_state>...</goal_state>` content against the insertion without applying the mutation again. This descriptive delimiter follows the repository's existing `<workspace_context>` convention and [Anthropic's published guidance to structure mixed prompt content with consistent descriptive XML tags](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#structure-prompts-with-xml-tags). That is public model-experience prior art, not evidence about any provider's proprietary training corpus. The session log is the only durable source of truth, so persistence and fork inherit goal records without another database or header field.
The replay fold validates JSON shape, source attribution, rendered content, fresh ids, revision continuity, lifecycle transitions, counters, and monotonic per-goal timestamps. Goal rounds are positive sequential `user/message` source numbers for the current active revision and cannot exceed `maxGoalRounds`; ordinary session turns do not affect the counter. A malformed current-format record fails replay rather than being ignored or repaired.
The replay fold derives mutations only from inbox insertions and validates JSON shape, source attribution, rendered content, message-id consistency on later admission, fresh ids, revision continuity, lifecycle transitions, counters, and monotonic per-goal timestamps. Goal rounds advance only from positive sequential admitted `user/message` source numbers for the current active revision and cannot exceed `maxGoalRounds`; ordinary session turns do not affect the counter. A malformed current-format record fails replay rather than being ignored or repaired.
When `Agent.inject()` defers a mutation inside an active tool batch, the service overlays the accepted payload in process memory so a later mutation can use its new revision. Reconciliation removes only an exact matching payload when the FIFO append becomes visible; reentrant append observers project each mutation exactly once. Incremental replay advances its cursor after each valid event and remains positioned at the first corrupt event, so later reads report the same durable fault. The durable log remains authoritative after restart.
The service reconciles an injected mutation with its synchronous inbox insertion by message id, so reentrant insertion observers apply each accepted change exactly once and a later admission only checks consistency. Incremental replay advances its cursor after each valid event and remains positioned at the first corrupt event, so later reads report the same durable fault. The durable log remains authoritative after restart.
### Lifecycle and live activation
@@ -36,12 +36,12 @@ The service accepts only the exact live `Agent` object registered under its id.
## Testing
Unit coverage pins creation defaults, exact-live-agent checks, compare-and-set rejection, every lifecycle transition, blocker reason validation and retention, cap enforcement on resume, clear/replacement, seeded replay and `SessionStore.fork()` inheritance, session-start and lifecycle-owner disarming, active-goal rearming, FIFO deferred mutation reconciliation, reentrant append observation, rejected-injection rollback, stable corrupt-event replay, service/listener disposal, listener containment, backward-clock clamping, strict record decoding, lifecycle continuity, source/content agreement, and sequential round attribution. A keyless Loader/stdio process test mounts the service and a lifecycle consumer through test-only `cordis.yml`, then reads the persisted JSONL externally to verify the model-visible snapshot and absence of an unrequested goal round. The package source is held to the repository's per-file 100% coverage gate.
Unit coverage pins creation defaults, exact-live-agent checks, compare-and-set rejection, every lifecycle transition, blocker reason validation and retention, cap enforcement on resume, clear/replacement, seeded replay and `SessionStore.fork()` inheritance, session-start and lifecycle-owner disarming, active-goal rearming, durable insertion folding, reentrant insertion observation, admission consistency, discarded-context persistence, rejected-injection rollback, stable corrupt-event replay, service/listener disposal, listener containment, backward-clock clamping, strict record decoding, lifecycle continuity, and sequential round attribution. A keyless Loader/stdio process test mounts the service and a lifecycle consumer through test-only `cordis.yml`, then reads the persisted JSONL externally to verify the goal record and absence of an unrequested goal round. The package source is held to the repository's per-file 100% coverage gate.
## Alternatives considered
- **Store goals in a separate database or session header** — rejected because the session log already supplies ordering, persistence, fork prefixes, and reconstructability; a second store introduces atomicity and lineage questions.
- **Use hidden log-only events** — rejected because durable state that changes future model behavior must be model-visible and reconstructable under the repository's logging invariant.
- **Use goal-only log events without queued context** — rejected because durable state that changes future model behavior must have a reconstructable path into model input. The inbox insertion supplies durable commit while retaining that model-facing path.
- **Persist activation and restart automatically** — rejected because opening or resuming a session must wait for human input; durable phase records status, not fresh authority to spend resources.
- **Count all session turns as goal rounds** — rejected because one session can contain human clarification, inspection, and unrelated work; only goal-attributed continuation turns consume this budget.
- **Add goal state or a generic loop abstraction to `dsh-agent-loop`** — rejected because state and continuation policy can compose through existing plugins, `Agent` verbs, and events without privileging the shipped loop implementation.
@@ -50,7 +50,7 @@ Unit coverage pins creation defaults, exact-live-agent checks, compare-and-set r
- Goal history survives persistence, resume, compaction of unrelated nodes, and session fork as ordinary session data.
- Resume and fork expose the same durable phase while remaining operationally inert until an explicit resume mutation arms activation.
- Full snapshots simplify inspection and strict replay but repeat the objective and state fields in model history until compaction shadows them.
- Full snapshots simplify inspection and strict replay; when admitted, they repeat the objective and state fields in model history until compaction shadows them.
- Revision and lifecycle validation reject tampered, partially written, or producer-inconsistent goal records early.
- Round caps bound continuation count only; policy consumers map round, token, currency, time, and provider limits to blocked reasons when they stop work.