Architecture
September 7, 2026 · View on GitHub
Context Guard separates DSH-owned execution from Guard-owned certification.
Responsibility boundary
| Module | Owns |
|---|---|
| DSH Goal | Persisted objective, automatic goal rounds, pause/resume/block/complete |
| DSH Todo | Lightweight current plan display |
| DSH Compaction | Model-visible history reduction without corrupting tool-call structure |
| Context Guard | Per-item task contract, revisions, evidence binding, and completion certificate |
Context Guard does not own Goal, Todo, Compaction, or continuation. It intervenes when completion is claimed without a valid certificate.
Durable state model
The effective plugin configuration and the DSH Session append-only log are the inputs to the rebuildable Guard projection. Context Guard appends no custom session event types: the persisted event vocabulary is harness-owned and the current persistence layer refuses unknown event types. The activation configuration supplies the initial enablement state, while all later session state is derived from the natively persisted events DSH already writes:
- effective plugin configuration — initial enablement (
opt-instarts disabled;alwaysstarts enabled before log replay); command/run— later enablement (/context-guard on|off|clear) and epoch transitions;user/message— captured contract clauses;tool/call+tool/result— bounded evidence and completion-certificate attempts.
The in-memory GuardProjection is a rebuildable cache: deriveProjection applies the effective activation configuration, replays the native log deterministically, recomputes every contract, evidence, and certificate, and flags corrupt when a recorded certificate no longer re-derives from the evidence in the log. The projection and its evidence are session-scoped: a new DSH session starts a new projection and cannot import, look up, or certify evidence IDs from another session. A completed workflow that needs a certificate must therefore produce its evidence and call context_guard_checkpoint in the same session. Under always, replay begins enabled; changing an existing profile from opt-in to always can therefore bring earlier persisted user messages into the derived contract. A recorded /context-guard off disables capture from that point until a later on. A recorded /context-guard clear supersedes every pending requirement and acceptance under a CLEAR:<revision> sentinel (prohibitions are retained) and bumps the contract revision, so a fresh empty-binding checkpoint can certify while the guard stays enabled. Captured contracts always carry a concrete subject/surface, so no unrelated evidence can close a requirement.
Synchronization
The runtime rebuilds the projection from the log before each step. Before evidence is produced, the runtime awaits ctx.sessions.flush(session); if no durability listener participated, evidence is marked durability-unknown, which fails closed.
The host lock uses dsh-core/v1: one exact 33-package DSH core, independent of optional market versions. The resolver walks all reachable runtime/profile dependencies, including those introduced by plugins, and checks installed manifests against lock identities. Missing, duplicated, mixed or changed core rows fail closed. Every replay rechecks the configured graph sources against the injected core identity before deriving certificate authority.
Core manifest version 2 and its policy/cohort values change the digest without changing the shared digest-v3 encoder. Legacy injected configuration requires explicit inspection and migration; historical records are never relabelled. Action capabilities are evaluated only after this core contract succeeds. Market restart additionally requires a trusted loaded-instance verifier. Current DSH supplies none, so restart is unavailable while unrelated guarded work remains supported. A requested restart stays pending. The version-2 service adapter distinguishes ordinary provider/instance drift from a persisted, target-bound restart handoff.
Evidence is produced only from persisted tool/call + tool/result pairs. Guard never inserts context between a Code Mode sub-call and its durable result.
Domain pipeline
classifyUserInteractiondrops session-layer utterances before capture: bare progression/acknowledgement phrases (继续,continue), meta questions (这个收尾具体要做什么,是不是bug), and meta comments/objections. The classifier fails closed — an artifact path, an explicit method, or a non-negated operation verb always keeps the message (or the individual clause inside a mixed message) a captured instruction.classifyClause/captureClauseclassify the remaining direct human message into requirement, acceptance, or prohibition.segmentAuthorityBlockskeeps quoted/code/reference material outside the contract, supports explicit section adoption, and captures uncertain material fail-closed.context_guard_evidenceproduces read-only resolution/state facts and validates already persisted effects;context_guard_actionowns the explicitly mutating install/apply/restart/publish and Git paths. Before any executable, command, HTTP request, or durable intent, the action tool flushes and replays the resolution/contract chain, then runtime authorization binds the action to one exact pending root-owned requirement/revision (never a prohibition or acceptance), rejects any matching pending root prohibition, and rechecks Guard integrity, host identity, semantic action, target-capture status, and every action-specific requested/resolved identity field. Exact package version/profile/registry and explicit Git remote/full refspec are required; resolution-only integrity, boot generation, and Git OIDs remain bound by the target digest and live prestate.evidenceFromPersistedToolResultmaps persisted results to versioned semantic actions, evidence roles, exact resolved targets, observed state, executable identity, immutable resolution-time expected transition, and bounded diagnostics. Only exact adapter and command-manifest IDs/versions in the shipped manifests may supply structured state facts. On Windows, audited.cmd/.batshims are version-probed through a closed invocation whose interpreter is pinned to the canonicalSystemRoot\\System32\\cmd.exerealpath and version. Resolution and effect bind both identities; execution reuses the revalidated paths and rejects shell-control or expansion characters rather than re-resolving the logical name throughPATHor trusting a changedComSpec. Modify transition construction first re-hashes the current source bytes against the resolved pre-digest, then applies the pinned unique UTF-8 replacement. Commit readback parses the raw post-commit parent tuple and accepts exactly one parent equal to the resolved pre-effect HEAD; fetch freezes that HEAD and requires the post-effect HEAD to remain identical. Pre-execute revalidation is a correctness gate, not isolation from a concurrently malicious process running as the same user; a mismatching post-action readback remains non-certifiable.certifyCheckpointflushes and resynchronizes first. Non-stateful actions require an action-compatible minimal facet; every action in the ten-memberSTATEFUL_ACTIONSset requires distinct resolution, effect, and independent state evidence, one shared resolved target, non-overlapping state observations, and an expected-transition predicate frozen and digested by the resolution fact before effect. The binding must reproduce that payload exactly before observed state is compared.- A certificate freezes protocol/certificate versions, epoch,
sessionRefDigest,hostLockDigest, contract revision/hash, explicit null-or-current Goal ref, open/evidence/binding digests, and the final certification digest. The tool computes a candidate without mutating runtime authority; only a persisted tool result that re-derives byte-for-byte becomes current authority. goalCompletionDenialrejects the Guard-ownedupdate_goal(action=complete)model-tool path before the Goal mutation unless that certificate is current. A trusted in-process producer can bypass the tool guard; replay detects the durablegoal/change completewithout a certificate and records an integrity violation, but the plugin does not claim to prevent or roll it back.context_guard_boundarypersists only qualifieduser_wait,external_wait, ordeferredcandidates. After the accepted tool result is flushed and replayed, the post-commit path reads the same active/armed Goal ref, callsgoals.disarm(agent), and independently reads the same ref as disarmed. Pre-effect, still-armed, and post-effect-unknown failures have separate fail-closed/fail-loud outcomes.decideTurnBoundaryreads only structured Guard/Goal state. Assistant completion prose is diagnostic and never a stop or continuation control source; active/armed continuation belongs to the Goal Round Driver.renderRecoveryPacketre-injects open requirements after compaction, resume, enable, rejection, or integrity loss. Injection is content-deduplicated: a re-armed packet with unchanged content is injected once, while resume, compaction, an enablement transition, relevant new evidence, or a new contract revision changes or forgets the digest. Unrelated historical evidence does not re-arm the same guidance.
0.4.2 capture and rebinding
A legal plugin user/message notice marks the 0.4.2 capture boundary. Earlier events keep the 0.4.1 clause segmentation and visual classification; the runtime does not rewrite old source events, pending items, evidence IDs, or historical certificates. New compound requests separate explicitly named actions and retain GUI/visual acceptance as non-certifiable work. Updating a package and recording a change do not automatically mean installing or committing it.
context_guard_rebind accepts operation: propose|query|withdraw, item_id, proposal_id, clauses, and optional clarification_item_ids. It has no model confirmation flag. A proposal's 1–8 clauses must concatenate to the complete original normalized text, and its serialized body must fit 8 KiB. Each clarification ID is either empty (keep the source clause) or names a later pending direct-root item that explicitly includes that clause and preserves captured identities and method constraints. The proposal records both root sources, item/revision identities, session, epoch, contract revision, candidate action/target/acceptance, and a digest. A GUI clause cannot be mapped to an installation-only result.
The tool returns a candidate without mutating the projection. A matching durable tool result registers the proposal on replay. Only a complete canonical root message 确认重绑定 <proposal ID> commits it. Confirmation rechecks the proposal against the current contract; changed, withdrawn, cross-session, or non-durable inputs keep the old item pending. Every child is constructed before the original is marked superseded. supersededByItems preserves the one-to-many relation, and reboundFrom preserves the proposal and confirmation source. A later root clarification keeps its own authority; partitioning alone cannot authorize a new mutation. Duplicate confirmation is idempotent.
No digest-v3 wire format or upstream fixture changes are needed. A replacement changes the item/status set and contract revision, invalidating the earlier certificate; evidence is never copied into a passed state. A fresh checkpoint must revalidate the exact action, target, executor, host lock, and transition. Evidence predating the authoritative root clause referenced by a replacement is rejected while retaining its historical ID.
Bounded queries and recovery
Checkpoint certification runs over the complete contract before display filtering. Default pages contain at most eight current items/constraints and ten evidence rows. Each list has its own cursor, bound to the session, epoch, contract revision, query, bindings, and evidence snapshot. history includes unsupported evidence with an unavailable disposition; default evidence and binding templates share action/target matching. Unknown cursor state is an explicit query error.
The serializer reserves metadata space before selecting rows and returns at most 12 KiB of valid UTF-8 JSON. Oversized rows identify omitted detail; detail_id retrieves bounded JSON-text chunks. Later chunks require the initial snapshot as detail_snapshot. A long ID may use a SHA-256 lookup token in the summary; its original ID remains unchanged in the detail. Explicit item_ids can inspect passed and superseded provenance as well as current work. None of these views changes the certificate's scope.
Recovery reserves its rules, query pointer and folding totals before filling item summaries. Separate category slots retain a key prohibition and the newest pending requirement before optional rejection details or evidence; many constraints cannot hide the current work. Long IDs are summarized independently of the reason and next step. Small packets distinguish capability restoration from requirement rebinding. Its default budget is 4,000 characters; values below 512 or non-integers are rejected. The 512-character form keeps a constraint, the current limitation, and a query pointer. Full constraint enforcement remains in the ledger and execution checks. The runtime re-derives binding refusals from persisted calls and deduplicates guidance against the current contract and relevant evidence. A real compact/resume boundary always resets that context-local deduplication.