# Raven v1 Architecture ## Alternatives compared ### Minimal one-entry-point design [`alternatives/minimal-interface.md`](./alternatives/minimal-interface.md) reduces the model interface to `advance` and `stop`. It has the highest apparent Depth and the smallest schema, but `advance` conflates task creation, state inspection, evidence capture, checkpoint publication, steering, verification, and completion. In the actual Harness execution model the main model—not the tool executor—does the web research and writing between tool calls. A magical `advance` would therefore hide which contribution is being committed and make failures difficult to diagnose. ### Default-first design [`alternatives/default-first-interface.md`](./alternatives/default-first-interface.md) makes a one-field request the common path and infers the Outcome. This is ideal for a hypothetical caller that asks Raven to run another model loop. Raven v1 is instead a native plugin inside the loop: the user already gives the request to the Harness agent, which uses Raven to maintain one durable Task. Repeating the request through the tool would add a second orchestration layer. ### Flexible contribution design [`alternatives/flexible-interface.md`](./alternatives/flexible-interface.md) makes state transitions and atomic evidence/artifact contributions explicit. It best fits the real execution boundary and gives strong Locality for Source-before-Claim validation. Its public verification profiles, compare-and-set revisions, and six lifecycle actions are more flexibility than v1 needs. ### Ports-and-adapters design [`alternatives/ports-adapters-interface.md`](./alternatives/ports-adapters-interface.md) correctly distinguishes current Harness dependencies from real variation. It avoids pass-through wrappers around `tools`, `systemPrompt`, and session metadata and introduces one real internal Seam, `SourceVerifier`, with production and deterministic Adapters. This is the strongest dependency design. ## Decision Raven v1 combines the flexible contribution design with the selective Seam placement of the ports-and-adapters design. The user-facing contract is one **Raven Task**. The native package contributes one model tool, `raven_task`, whose action variants maintain that Task, and one compact prompt section that tells the existing Harness agent how to use it. The tool does not run another model, scheduler, or public workflow. The Harness agent performs research and writing with its existing tools, then atomically contributes Sources, Claims, Limitations, and an Artifact Checkpoint to Raven. This preserves a deep external Module while avoiding a semantically overloaded `advance` operation. Tool actions are an internal protocol for the model, not new user-visible tasks or stages. ## External Interface The Cordis plugin exports named `name`, `inject`, and `apply` values. It has no default export and provides no process Service. It ships two halves in one package: the Host half above, and a browser half at `./client` whose only contribution is one card on the Settings › Plugins page. ```ts type RavenTaskAction = | { action: "start" outcome: "research" | "general-writing" | "academic-writing" | "learning" request: string grounding?: "required" | "optional" | "none" } | { action: "checkpoint" taskId: string stage: "discover" | "read" | "analyze" | "draft" | "verify" | "refine" summary: string artifact: string sources?: SourceInput[] claims?: ClaimInput[] failures?: FailureInput[] } | { action: "discover"; taskId: string; queries: string[] } | { action: "draft"; taskId: string; instruction: string; routes?: string[] } | { action: "steer"; taskId: string; correction: string } | { action: "complete"; taskId: string; artifact: string } | { action: "status"; taskId?: string } | { action: "stop"; taskId: string; reason?: string } | { action: "resume"; taskId: string } | { action: "export"; taskId: string; title?: string; tags?: string[]; init?: boolean } ``` The tool is exclusive by default because it does not opt into Harness parallel tool execution. Same-Task mutations therefore serialize inside one agent without adding an `expectedRevision` field that the model must echo. The state still carries a monotonic revision for audit and replay validation. The tool schema enforces shape, enums, required fields, and unknown-key rejection. The current Harness JSON Schema subset has no `maxLength` or `maxItems`, so bound descriptions are model guidance—not registry constraints. The engine and replay codec are the executable enforcement owners for every text/collection ceiling, including direct callers that bypass the registry. ### Why these actions exist - `start` establishes one Task identity and Outcome before work is recorded. The evidence floor belongs to the Outcome, not to the executor: `research` and `academic-writing` default to `required` and may be narrowed to `optional`, but never to `none`. An executor that could switch its own floor off could relabel ungrounded prose as research and still complete cleanly. - `discover` runs ONE batch of complementary queries through the Harness `ctx.web` search half and returns Leads. It is a separate action rather than a `checkpoint` field because finding candidates and committing evidence are different authorities: a Lead has been located, not read, so it may never reach a Claim, an Artifact citation, or the evidence floor. The batch is the unit on purpose — several angles share one deadline, deduplicate against each other, and cost one Task step, which is what makes a wide first sweep cheaper than a sequence of narrow ones. A query that fails becomes a `tool` Limitation on the Task instead of aborting the batch, so the Task keeps the angles that worked and still records the angle it lost. - `checkpoint` atomically commits an independently useful Artifact plus the evidence and failures that inform it. Stages are observations, never approval gates. - `draft` asks every configured model route for the same bounded instruction and returns the candidates side by side. It is a separate action for the same reason `discover` is: producing candidate wording and committing evidence are different authorities. A Draft Variant has been written, not verified, so it may never reach a Claim, an Artifact citation, or the evidence floor. The deployment owns the route list and the agent may only select a subset of it, because naming a model is naming spend and a data path. A route that fails becomes one labelled variant rather than a failed round, so the comparison survives one dead provider. - `steer` appends a Steering Revision to the same Task and invalidates stale final verification. - `complete` verifies the exact candidate Artifact and recorded source references. It either completes, completes with explicit Limitations, or returns actionable issues while leaving the Task active. - `status` reconstructs compact state after resume or compaction. - `stop` and `resume` let the user interrupt work without losing or replacing the Task. There is deliberately no public Source CRUD, Claim CRUD, worker management, verification job, workflow stage controller, or approval action. ## Domain records ### Task state One compact JSON state contains: - Task ID, Outcome, request, grounding policy, phase, monotonic revision, and times; - Steering Revisions; - immutable Checkpoint descriptors and the latest Artifact; - normalized Sources and Claims; - Source/tool/coverage Limitations; - the most recent verification receipt and exact Artifact SHA-256. Task phases are `active`, `stopped`, `completed`, and `completed-with-limits`. Normal research stages do not appear as phases. ### Lead A Lead is a candidate Raven located but did not inspect: URL, optional title, snippet, and publication label, plus the queries that surfaced it. Leads are returned by `discover` and never enter Task state — they are not evidence, they do not survive a session reload, and re-running discovery is cheaper than storing them. A candidate returned by several queries is one Lead recording all of them: breadth information for the agent's next move, explicitly not corroboration, because one backend answering twice is still one observation. ### Source A Source has a stable ID, canonical HTTP(S) URL, title, locator, bounded excerpt, optional role/family/as-of metadata, inspection time, and source-check result. Registration rejects credential-bearing URLs, duplicate identities, missing locators/excerpts, and any attempt to change the URL, title, locator, excerpt, role, family, or as-of metadata behind an existing ID. Exact resubmission is idempotent and preserves the prior verification record; changed evidence requires a new Source ID. A search result, snippet, remembered citation, or worker mention is not a Source until the main agent has inspected it and supplies the locator and bounded verbatim excerpt. Before an externally grounded Checkpoint is published, Raven independently reopens the URL and requires that excerpt to occur in the retrieved body after HTML/entity and whitespace presentation normalization. ### Claim A Claim has a stable ID, text, `external | analysis` kind, `material | context` importance, `supported | qualified | deferred | rejected` disposition, Source IDs, and optional `contradicts` links to Claims it genuinely conflicts with. A supported or qualified external Claim cannot have an empty, unknown, or failed Source set. Reusing a Claim ID for different text is rejected rather than silently rewriting provenance. Contradiction links must resolve to Claims in the same Task and cannot be self-referential; they are validated after the whole batch so a mutually contradicting pair can be submitted together. The rendered Claim trace marks both sides contested, so genuine disagreement between authorities survives instead of one side being silently dropped. ### Artifact and citations Artifacts cite Sources with `[@source-id]` tokens. Raven validates the tokens and mechanically renders them as Markdown links from Source records, followed by a Sources list and a generated Claim trace that maps every material supported/qualified Claim ID and escaped text to its Source IDs. Source titles, locators, and Claim text are Markdown/HTML escaped before rendering. Unknown IDs and unregistered raw external URLs are rejected. This keeps the URL and Claim↔Source mapping outside model memory; literal anchor matching does not replace main-agent semantic entailment judgment. The Claim trace also annotates independence. A Claim citing two or more Sources that all declare one `sourceFamily` renders as a single family and explicitly not as independent corroboration, and undeclared families render as unverified rather than being assumed independent. Family is declared by originating record and institutional lineage, never derived from host: outlets republishing one wire item are one family, while distinct formal documents on one host may be separate families. Raven does not mechanically require multiple families, because independence only counts for the same atomic proposition — two Sources each supporting a different clause of a compound Claim are not cross-verification, and that judgment stays with the agent. Raven enforces only what it can verify and makes the rest impossible to misread. A Checkpoint stores its immutable ordinal, stage observation, summary, Artifact SHA-256, character count, creation time, and applied Steering Revision. The latest Artifact content remains in compact state; older full contents already live in prior durable tool results, avoiding quadratic snapshot growth. Request, Artifact, summary, correction, Source, Claim, Limitation, Checkpoint, and Steering collections all have executable size ceilings shared by action validation and replay decoding. ## Progressive execution The prompt requires this observable cadence for substantial work: ```text start Raven Task → use existing Harness tools to inspect an initial credible source set → reopen the recorded URLs and match the bounded excerpts → checkpoint a useful outline, draft, explanation, or findings → continue research and evidence checks without asking permission → apply user correction as a Steering Revision on the same Task → emit a revised Checkpoint for every substantive final edit → complete only those exact latest Checkpoint bytes ``` A Checkpoint result renders the useful Artifact immediately while the Task remains active. The user can react to it while the Harness agent proceeds through later model/tool steps. The system never treats a progress slogan as a Checkpoint. ## Completion and graceful degradation `checkpoint` and `complete` both ask the `SourceVerifier` to reopen relevant URLs and match recorded excerpts. `complete` also performs deterministic checks against the exact latest Checkpoint fingerprint. Completion is rejected, with no state loss, when: - the final Artifact has unknown citation IDs or unknown raw URLs; - no useful prior Checkpoint exists; - the latest Steering Revision has no subsequent Checkpoint; - the candidate bytes differ from the exact latest Checkpoint fingerprint; - a material supported/qualified external Claim lacks a Source citation in the final Artifact; - a cited Source is broken, unavailable, or lacks its recorded excerpt; - a grounding-required Task has no material supported/qualified external Claim with at least one currently reachable, excerpt-matched Source. A zero-valid-work result remains active even when its coverage failure is honest. The Task becomes `completed-with-limits` rather than failing wholesale when the Artifact remains useful after failed dependencies have been removed from accepted support, affected Claims are deferred, and coverage Limits are explicit. A broken or unverifiable Source cannot appear in the completed Artifact as accepted support. Independent verified Sources, Claims, and Artifact sections survive. When a Source later fails verification, Raven automatically defers every supported/qualified Claim whose usable support set becomes empty and records a Source Limitation. Every substantive final edit must first become a Checkpoint; Completion requires the candidate SHA-256 to equal that exact latest post-steer Checkpoint fingerprint. Tool, worker, or scheduler completion is never Raven Completion. ## Internal Modules ### Task engine A pure Task engine parses one action, restores the latest state, proposes a complete next state, validates invariants, and returns either the next state or an actionable non-mutating result. It owns all lifecycle, revision, Checkpoint, Source, Claim, citation, and completion semantics. ### Prose Layout A pure, total, idempotent Module normalizes every submitted Artifact into the Task's canonical line shape before it is hashed and stored. Idempotence is a hard requirement rather than an elegance: Completion compares Artifact byte hashes, so a caller must be able to resend either its own packed text or the bytes Raven returned without being told it made an unauthorized final edit. See [ADR 0003](../adr/0003-prose-layout.md). ### Draft generator Seam Drafting is a third Raven-owned Seam, kept separate from both evidence Seams because producing candidate wording and confirming evidence are different authorities: ```ts interface DraftGenerator { generate(request: DraftRequest, signal: AbortSignal): Promise } ``` `HarnessLlmDraftGenerator` reads `ctx.llm` dynamically and runs every route concurrently under its own deadline. Two properties of that seam shape the Adapter. It reports adapter, dispatch, and iteration failure through a terminal `finish` chunk rather than by throwing, so the finish reason is inspected explicitly — a drafter that only wrapped the loop in `try`/`catch` would accept an empty or truncated draft as a real one. And it applies no retry or metering to a plugin-initiated call, which is recorded here rather than assumed. See [ADR 0004](../adr/0004-draft-variants.md). ### Source searcher Seam Discovery is a second Raven-owned Seam over the SAME official capability: ```ts interface SourceSearcher { search(request: { queries: readonly string[]; maxResults: number }, signal: AbortSignal): Promise } ``` `HarnessWebSourceSearcher` reads the optional `ctx.web` search half dynamically, like the verifier reads its fetch half. It mirrors the Harness `web_search` tool where the semantics are the tool's to define — the batch bound is applied before deduplication, the per-query source bound is passed through, and candidates merge round-robin by rank with exact-URL deduplication — and departs from it in exactly one place, deliberately: the Harness tool cancels every sibling query as soon as one fails, because a model-facing search either answers or errors, while a Raven batch is a Task step whose successful angles are already paid for. Each query therefore carries its own deadline, one failure yields a `tool` Limitation rather than a batch error, and only caller cancellation aborts everything. Withheld discovery (`sourceDiscovery=disabled`) and an uncomposed search provider report the same way an absent capability does — unavailable with the reason named — because an empty result set would read as "nothing exists". ### Source verifier Seam This is the second Raven-owned infrastructure Seam: ```ts interface SourceVerifier { verify(sources: readonly SourceRecord[], signal: AbortSignal): Promise } ``` Two real Adapters justify it: 1. `HarnessWebSourceVerifier` dynamically reads the optional `ctx.web` capability, reopens URLs with the tool cancellation signal, normalizes HTML/entity/whitespace presentation, rejects cross-host resolution, and marks a Source reachable only when its bounded excerpt occurs in the retrieved body. Provider absence, identity drift, or mismatch is never fabricated success. Extraction distinguishes block-level from inline elements: block boundaries emit one separator, inline markup emits none, so `precise`, `50th`, and inline-wrapped CJK do not produce false anchor failures. On mismatch the adapter reports the longest matching prefix plus the nearest retrieved passage so the agent repairs the anchor instead of retrying it unchanged, and separates a partial divergence from an excerpt absent entirely — the latter is a fabrication signal rather than an anchor defect. A `truncated` retrieval is reported as `unavailable`, not `failed`: a cut-off body cannot disprove an excerpt drawn from the tail. Both outcomes block publication, but only one of them accuses the agent of fabricating a quotation, and the wrong label would push it to weaken a correct excerpt until it fits the visible prefix. 2. `DeterministicSourceVerifier` supplies reachable, failed, redirected, unavailable, and cancelled outcomes for tests. The Adapter reports observations only. Responses must match the requested Source ID set exactly once per Source and pass runtime validation for status, time, HTTP code, and resolved URL; protocol violations conservatively make the requested Sources unavailable. Provider calls are raced against cancellation so an adapter that ignores `AbortSignal` cannot hold Raven open. Claim and Completion policy remains in the Task engine, preserving Locality. ### Direct Harness integration Raven directly uses the current required `tools` and `systemPrompt` Interfaces. It does not create pass-through ports for one implementation. The plugin stores schema-versioned compact state in official `tool/result.meta`. A versioned codec recursively validates all root fields, nested records, allowed key sets, size ceilings, unique identities, counters, URLs, hashes, evidence links, and phase invariants; malformed, unknown-version, or unknown-field snapshots are skipped so an older valid snapshot can be restored. Metadata v2 records the updated Task plus the Session's `currentTaskId`; replay scans every Raven result and rebuilds a `session → taskId → state` registry instead of retaining only the latest Task. At most one Task may be active in a Session, status inspection of history does not change the current Task, resuming an older Task requires the current one to be stopped, and new Task ordinals use the Session-wide maximum. An in-memory registry covers calls before results are durably appended; replay metadata remains the restart source of truth. A Code Mode sub-call gets no result card, so its record cannot ride `tool/result.meta`. It rides the durable copy of the sub-dispatch instead, through the official `tools/code-dispatch-log` waterfall, as a base64 payload inside an HTML comment on the Harness-owned `tool/code-dispatch` event. A plugin-owned session event type is explicitly rejected as the mechanism: the Harness persistence read path refuses to interpret a stored log containing an event type it does not know unless the writer marked it `ignorable`, and `Session.append` exposes no way for an out-of-repo plugin to set that marker, so one Code Mode Task step would make the whole session unloadable. Base64 keeps a Task Artifact that happens to contain `-->` from closing the comment. The path degrades safely rather than exactly: a spill policy that replaces an oversized log copy loses that one step, and the next direct call republishes the complete record. Agent Teams is consumed the same way `web` is — dynamically, never injected — but with one additional constraint: the Harness Team packages are `private: true`, excluded from the release payload, and carry no stability promise, so Raven may not import their types or declare a peer dependency. It mirrors structurally the one method it reads, `tryMembership(agent)`, contains every call, and keys the Task book by the returned Team id so a Team shares one Task. Every failure mode — no capability, no membership, a throwing probe — degrades to the single-agent book. Long-running continuation, subagents, and workflows remain ordinary Harness tools available to the main agent. The prompt may recommend them proportionately, but the Raven package neither wraps nor requires them. Their topology never enters Raven Task state. ## Durable output: llm-wiki emission A Task that only renders into chat evaporates at session end, so `export` projects the Task into llm-wiki page bytes: one artifact page under `wiki/queries`, one immutable `wiki/raw` page per Source, and one appendable `wiki/log.md` entry. `init` additionally seeds `SCHEMA.md`, `index.md`, and `log.md`, so a repository Raven starts is a valid llm-wiki rather than a Raven-specific format. The projection is pure and Raven never writes files; the agent writes the returned bytes with ordinary Harness file tools. That keeps the plugin free of a filesystem dependency and keeps the write inside the agent's existing approval and sandbox boundary. Frontmatter is derived, never asserted. `sources:` comes from the registered Sources, `contested: true` from Claim contradiction links, and `confidence` from the Task phase and its recorded limits — an unfinished Artifact is `low`, any limitation or deferral caps it at `medium`, so a page cannot silently harden into wiki fact. Each `raw/` page carries `capture: excerpt-only` and a `sha256` over exactly its own body: Raven stores the verified excerpt plus its verification receipt rather than a full page capture, so the digest detects drift in what was stored and the difference from a full-body llm-wiki ingest stays visible. Scale, quality, and insight machinery from that skill — lint/health scripts, index regeneration, tier promotion, stub materialization, log rotation — is deferred. See `docs/adr/0002-llm-wiki-repo-format.md`. ## Cordis lifetime and composition The plugin declares `inject = ["tools", "systemPrompt"]`. It registers one scoped prompt section, one scoped tool, and one scoped `agent/pre-step` listener that injects only a compact active-Task summary after resume. All registrations are owned by the Cordis fiber and disappear on unload. The package publishes no service. A row in a user-authored agent preset can therefore sit loose without an isolate realm. Shipped presets and the Harness host composition remain untouched. ## Package layout ```text src/ domain.ts # owned JSON types and guards route.ts # dependency-free model-route and mode vocabulary, shared by both halves prose.ts # pure, idempotent, Markdown-aware Prose Layout engine.ts # deep Task Module codec.ts # replay validation of the compact snapshot wiki.ts # llm-wiki page projection config.ts # deployment settings schema prompt.ts # concise stable protocol plugin.ts # direct Harness registrations and the web, model, and settings Adapters index.ts # named Cordis exports client/ # browser half: one Settings > Plugins card card-state.ts # pure form model: validity, override detection, save planning controller.ts # staged edits and writes over the settings scope Card.tsx # presentation only slot-contract.ts # the targeted slot augmentation, restated (see below) index.ts # named browser Cordis exports tests/ unit/ # engine invariants, Prose Layout, card form model, bundle manifest acceptance/ # four Outcomes and progressive/steering/failure scenarios integration/ # plugin load/replay/packed-consumer/browser-artifact checks ``` The package is one ESM release unit shipping two halves, and it declares no runtime npm dependency of its own. The Host half never bundles a Harness package. A profile installs plugins with `nodeLinker: hoisted` and `autoInstallPeers: false` precisely so an out-of-tree plugin's peers fall through to the running installation and every plugin shares ONE cordis instance; a bundled copy would give this plugin a second instance whose services the Harness cannot resolve, and that failure looks like an absent service rather than a build error. The browser half inverts that rule: it inlines everything except the eight specifiers the shell seeds into its own module table, because `require` inside the generated factory is that table's shim rather than Node's resolver, and an unanswerable specifier is a guaranteed runtime throw. ### Browser half and the restated slot contract The card reaches the page through the keyed `settings.plugin.item` slot, whose key is the settings namespace. That keying is what lets a plugin distributed outside the Harness repository contribute a card at all: the Host half registers the namespace, the browser half registers a card under the same key, and the tab pairs them without ever learning what the namespace means. The declaring package's own augmentation cannot be imported across the client bundle-purity boundary, and its published copy lags the running Harness — at `0.1.0-rc.6` the slot is `kind: 'list'`, at `0.1.0-rc.8` it is `kind: 'keyed'`. A card registered under the older shape compiles and then never renders, with nothing logged anywhere. `src/client/slot-contract.ts` therefore restates the targeted augmentation, and `scripts/verify-dsh.ts` asserts that shape against the Harness checkout under test, so the drift breaks the release gate rather than the browser. The Harness card chrome and staged-form model are likewise off limits as values, so the card reimplements them. All of that logic lives in `card-state.ts` and is pure — a reimplementation is exactly the thing that drifts, and purity is what makes every rule in it testable in Node without a browser. ### Composition surfaces The package declares `dsh.bundle.patch`, so `dsh plugin add` appends it to a profile's bundle list and `cordis.patch.yml` inserts one host-plane row. Host plane rather than an Agent Preset, deliberately: Raven publishes no Service, but its settings namespace and its `tools/code-dispatch-log` waterfall are both process-wide, and a namespace served only while a session using one preset happens to be alive would appear and vanish in the settings UI. `examples/agent-row.cordis.yml` remains the preset-scoped alternative; mounting both registers `raven_task` twice into two different layers. ## Agreed test Seams The direct objective already fixes the test surfaces: 1. **Cordis load Seam:** plugin exports survive real Loader unwrapping; tool, prompt, and listener mount and dispose cleanly. 2. **Raven Task Interface:** all four Outcomes use one Task identity; Checkpoints, Steering Revisions, stop/resume, replay, and terminal dispositions obey the executable contract. 3. **SourceVerifier Seam:** production-shaped and deterministic Adapters produce the same completion policy, including unknown citations, broken links, optional capability absence, cancellation, and independent partial-result survival. 4. **Durable Task-state Seam:** a direct call publishes Task state as result metadata and a nested Code Mode sub-call publishes the same record as a plugin-owned session event; each path alone rebuilds the Task book on replay, and neither duplicates the other. A host exposing a read-only session view keeps working on result metadata. 5. **Failure Recovery Seam:** the tool-owned content finalizer attaches the addressed Task's identity and recovery action to a failed outcome, including the invalid-argument and cancellation paths the output projection never sees, and is total: a hostile execution view preserves the content instead of replacing one failure with another. 6. **Settings Seam:** the `raven-research` namespace registers with the composition entry as its `base` layer; a resolved section takes effect on the next Source check, an absent settings service leaves the entry authoritative, and no setting lowers a Task's evidence floor. Tests assert canonical state and dispositions rather than brittle generated prose or a specific agent graph. ## Compatibility target Raven v1 targets DeepSeek Harness `0.1.0-rc.8` at commit `141eb6fef83422698aef7a981029e843e8161534`, Node `^22.19.0 || >=24`, and pnpm `11.21.0`. Release checks use built ESM and declarations, a real Loader-path smoke test against that checkout, and a packed clean-consumer install. The version is an RC, so the package claims only the exact tested compatibility family.