# Conflicts: # .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml # .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md # .agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml # .agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml # .agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md # .agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.i18n.yaml # .agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml # .agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml # .agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md # .agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml # .agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md # .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml # .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md # .agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.i18n.yaml # .agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md # .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml # .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md # .agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml # .agents/notes/implemented/architecture/2026-07-15-replay-token-meter-service.i18n.yaml # .agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.i18n.yaml # .agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml # .agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md # .agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml # .agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md # .agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml # .agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md # .agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.i18n.yaml # .agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.i18n.yaml # .agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.zh.md # .agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.i18n.yaml # .agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md # .agents/notes/implemented/feature/2026-06-24-workspace-context.i18n.yaml # .agents/notes/implemented/feature/2026-06-24-workspace-context.zh.md # .agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml # .agents/notes/implemented/feature/2026-06-30-hook-protocol-lib.i18n.yaml # .agents/notes/implemented/feature/2026-06-30-hook-protocol-lib.zh.md # .agents/notes/implemented/feature/2026-06-30-interception-seams.i18n.yaml # .agents/notes/implemented/feature/2026-06-30-interception-seams.zh.md # .agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml # .agents/notes/implemented/feature/2026-07-06-sandbox.zh.md # .agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.i18n.yaml # .agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.zh.md # .agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml # .agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md # .agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml # .agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml # .agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.i18n.yaml # .agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.zh.md # .agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml # .agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.i18n.yaml # .agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.zh.md # .agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml # .agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md # .agents/notes/implemented/feature/2026-07-27-tmux-location-context.i18n.yaml # .agents/notes/implemented/simplification/2026-06-20-public-agent-stop-surface.i18n.yaml # .agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.i18n.yaml # .agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.zh.md # .agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.i18n.yaml # .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml # .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md # .agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.i18n.yaml # .agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.i18n.yaml # docs/core-data-structures/compaction.i18n.yaml # docs/core-data-structures/goal.i18n.yaml # docs/core-data-structures/goal.zh.md # docs/core-data-structures/llm-streaming.i18n.yaml # docs/core-data-structures/session.i18n.yaml # docs/core-data-structures/session.zh.md # docs/core-data-structures/skills.i18n.yaml # docs/core-data-structures/skills.zh.md # docs/core-data-structures/system-prompt.i18n.yaml # docs/defensive-patterns.i18n.yaml # docs/defensive-patterns.zh.md # docs/user/develop/framework/events.i18n.yaml # docs/user/develop/framework/events.zh.md # packages/acp/acp/README.i18n.yaml # packages/client/ui-goal/README.i18n.yaml # packages/compact/compact-basic/README.i18n.yaml # packages/compact/compact/README.i18n.yaml # packages/context/time-context/README.i18n.yaml # packages/context/tmux-context/README.i18n.yaml # packages/core/agent-loop/README.i18n.yaml # packages/core/agent-loop/README.zh.md # packages/core/agent/README.i18n.yaml # packages/core/agent/README.zh.md # packages/core/session/README.i18n.yaml # packages/core/session/README.zh.md # packages/core/system-prompt/README.i18n.yaml # packages/core/system-prompt/README.zh.md # packages/examples/cli-demo/README.i18n.yaml # packages/goal/command-goal/README.i18n.yaml # packages/goal/goal-session/README.i18n.yaml # packages/goal/goal/README.i18n.yaml # packages/goal/tool-goal/README.i18n.yaml # packages/goal/tool-goal/README.zh.md # packages/guard/README.i18n.yaml # packages/guard/README.zh.md # packages/guard/repeat-tool-guard/README.i18n.yaml # packages/hooks/hooks-claude/README.i18n.yaml # packages/hooks/hooks-codex/README.i18n.yaml # packages/host/apiproxy/README.i18n.yaml # packages/host/apiproxy/README.zh.md # packages/llm/llm/README.i18n.yaml # packages/plan/plan-mode/README.i18n.yaml # packages/plan/plan-mode/README.zh.md # packages/sdk/sdk-client/README.i18n.yaml # packages/sdk/sdk-client/README.zh.md # packages/sdk/sdk-protocol/README.i18n.yaml # packages/session-persistence/session-persistence/README.i18n.yaml # packages/session-persistence/session-persistence/README.zh.md # packages/skill/tool-skill/README.i18n.yaml # packages/subagent/subagent-dsh-sdk/README.i18n.yaml # packages/subagent/subagent-inprocess/README.i18n.yaml # packages/subagent/subagent-inprocess/README.zh.md # packages/ui/jsonrpc/README.i18n.yaml
@deepseek-ai/dsh-session-projection
English | 中文
Session-projection seam. It owns ctx.sessionProjections, the registry that DRIVES every registered projection unit forward over committed session events and serves finished whole values to carriers (the api-proxy history tail page and session/projection push frame today; TUI/ACP/headless consumers later). A domain registers pure mathematics; the framework owns the drive. Design authority: the session-projection RFC.
Service: SessionProjectionRegistry (ctx key: sessionProjections)
Public API
ctx.sessionProjections.register(definition): () => voidRegister one domain's unit. Duplicate keys and invalidstateVersionthrow; the registration is an effect on the calling fiber, so an unloaded domain plugin's key (with its cached cells) disappears from subsequent drives and snapshots — clients read that as capability absence.ctx.sessionProjections.onChanged(listener): () => voidSubscribe to the change feed: one call per unit whose state reference changed, per committed event, carrying the schema-validated view and the causing seq. Effect-tied likeregister.ctx.sessionProjections.snapshot(session): ProjectionSnapshotOne consistent synchronous cut over every registered unit —{ asOfSeq, values }withasOfSeq= the seq of the last event every value reflects (-1for an empty log).
Key Types
SessionProjectionMap— the single merge-extensible type table for the whole chain (host unit, wire block, React hook). Values are wire-JSON whole values; rendering belongs to the slot system, never this layer.ProjectionDefinition<K, S>—{ key, schema, init(), apply(state, event), view(state), stateVersion }: a state-driven computation unit of three pure synchronous functions plus declarations, never an opaque getter.
Contract
- The framework drives, the domain computes. The registry subscribes to
session/eventonce; every committed event passes every unit'sapplyeagerly. Domains hold no subscriptions. Cells ({state, observedSeq}per unit per session, WeakMap-keyed) build lazily — a unit registered after events flowed, or a read of a session predating the registration, foldsinitover the in-memory log on first touch. - Same-reference means no work.
applyMUST return the same state reference for events that do not concern the unit; the drive gates the change feed onObject.is, so non-matching events cost one call and nothing downstream. - Whole-value event rule (load-bearing). A state-carrying log event MUST carry the complete post-change state, never a bare delta — it keeps every transition trivially cheap and every served value self-describing (last-wins for consumers).
- Synchronous unit discipline.
init/apply/viewMUST be synchronous; carriers readsnapshot()in the same tick as their page slice, which is what makesasOfSeqone consistent cut. An accidentally-asyncviewreturns a Promise, which fails the boundaryschema.parseloudly. - State is plain JSON,
stateVersionis its invalidation anchor. The persisted projection cache stores(sessionId, key, ver, seq, val)rows; bumpstateVersionwhenever the state shape or the fold semantics change so stale rows are discarded instead of forward-applied into garbage. - No wire vocabulary here. The registry exposes only the change feed and the snapshot read face; carriers (api-proxy) mint their own frames (
session/projection) and blocks from them. - Optional seam. Domain plugins register under
ctx.inject(['sessionProjections'], …)so headless assemblies without the registry stay unaffected; carriers usectx.get('sessionProjections')and omit their block/frames entirely when the registry is absent.
Role
This is the interface-plus-drive package of the capability-seam split: domain host plugins (e.g. dsh-tool-todo) contribute units, carriers (dsh-host-apiproxy) consume the snapshot and change feed, and neither knows the other.
Model Experience
None, as the registry only computes client-facing read models of already-logged session state and touches no prompt, message, schema, stream, or tool result.
KV Cache effect
None; projections never assemble or send provider requests.
Known Limitations and Deferred Work
- Every tail page carries every registered key — there is no per-key opt-out or lazy-key request shape yet; acceptable while values are UI-scale whole states (a todo list, a goal snapshot), revisit if a domain's value grows large.
- Eager drive touches every unit per event — cheap by construction (whole-value rule, same-reference gate), but a hot path would justify per-unit event-type prefilters, addable without contract change.
- Registry cells live in memory only — a restart rebuilds by folding the log on first touch; compositions that mount
dsh-session-projection-cacheseed that fold from persisted rows instead. - Synchronous unit discipline is only partially mechanical — the boundary
schema.parserejects a Promise-returningview, but anapplythat blocks or reads torn non-session state is a review concern; the invariant companion documents why no runtime check exists.