Files
deepseek-harness/.agents/notes
kingwl 6adb14b56d feat(telemetry): adopt from the construction boundary — constructor seeds never re-export
A cursor-less adoption (process restart + resume, fork, seam-module
reload) replayed the session's full log from seq 0, re-exporting
history that already left the process — a resume re-billed its entire
stored log on every restart, and a fork re-shipped the parent's prefix
under the child's id, doubling query-time counts on OTLP backends with
no native ingest dedupe.

dsh-session now exposes the fact the constructor already validated but
discarded: Session.firstLiveSeq, the constructor-seed length — the
first seq appended in this process. header.seedLength cannot serve
here: it is the durable fork-lineage boundary, and a resumed session's
constructor seed is its full stored log while the header keeps the
original fork value (llm-replay and session-query-sqlite depend on
that meaning). Constructor seeds also never publish on the
session/event firehose, so adoption replaying them was inconsistent
with the system's own publication semantics.

Adoption's cursor-less fallback starts at firstLiveSeq; seed events
still feed the chunk projection, so mid-step continuations re-drop
after a resume. Fork streams are no longer self-contained: records now
carry session.seed_length (with the existing session.parent_id) so
receivers stitch the child's stream onto the parent's. Accepted cost,
consistent with at-most-once delivery and recorded in the revival
Agent Note: a resume no longer backfills records a previous process
failed to deliver — a deployment with that requirement needs the
deferred outbox, not replay.

Pinned red-first: seeded adoption exports nothing (assertion reversed
from the prior seed-readback test, obsolete behavior changed with its
test), resume-shaped seed rebuilds the projection without exporting,
and fork records carry the stitch attributes.
2026-07-27 18:35:03 +08:00
..
2026-07-19 22:52:03 +08:00

Agent Notes

English | 中文

One kind of design doc lives here. An Agent Note records a decision or proposal that shapes this codebase — the why and what we gave up, the parts code and docs can't carry. This file is the front door and contract: where Agent Notes live, when to write one, and the in-file format.

Layout and naming

Every Agent Note has two axes, both encoded in its path — {lifecycle}/{class}/yyyy-mm-dd-topic-title.md:

  • Lifecycle (the top-level folder) is the Agent Note's status, and an Agent Note moves between folders as that status changes:
    • proposed/ — proposals reviewed before implementation; not yet built (or only partly).
    • implemented/ — the decision shipped. The file records what was decided and what was rejected, and is kept current with what actually shipped: when the code later moves a file, renames a package, or changes a key/default, the Agent Note is updated in the same change to match (facts only — paths, names, structure — not the decision itself). See implemented/AGENTS.md.
    • rejected/ — the proposal was considered and declined. Kept for the record so the rejection isn't re-litigated.
  • Class (the nested folder) is the kind of decision — see Classification below.

The date in the filename is when the topic was first proposed (per git history). Cross-references between Agent Notes use relative markdown links ([topic](../../implemented/architecture/2026-…-….md)) — never bare prose or numbers — so they are mechanically checkable and survive moves between folders.

The tree is the inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized INDEX.md; the no-index Agent Note owns the rationale.

Classification

Each Agent Note belongs to one path-encoded class from the closed set in scripts/agent-note-tree.ts; the classification gate rejects other folders. Adding a class requires updating the canonical set and this section. See the classification Agent Note.

Class What it covers
feature A new user- or model-facing capability.
bug-fix Corrects a defect or closes a gap a postmortem surfaced.
simplification Removes code, behavior, or surface area without adding a capability.
architecture A structural decision about the shipped source — how packages relate, what the runtime vocabulary is.
process Tooling, policy, or workflow around the code — gates, the package manager, vendoring — not runtime behavior.
testing Test infrastructure and strategy.

The architecture / process line: architecture is about the source we ship; process is the surrounding tooling and workflow. (refactor is deliberately absent — it overlaps simplification, whose discriminator, "does observable behavior change?", already covers it.)

When to write one

Every non-trivial change MUST add or update at least one Agent Note in the same PR. A change is non-trivial when it alters behavior, architecture, a cross-file or cross-package contract, process or tooling, testing strategy, an on-disk, wire, or configuration format, or another decision a maintainer may reasonably revisit. A proposal for substantial future work starts in proposed/; a decision already made starts in implemented/. Pick the class folder that matches the decision (see Classification).

Updating the Agent Note that already owns the decision satisfies the rule; do not create a duplicate. Only a purely mechanical or local edit with no behavioral, contractual, structural, process, or rationale change is exempt. An Agent Note is never edited into a different decision: supersede it with a new one, and keep both notes cross-linked unless the old note is later fully consolidated under the rule below. Editing an implemented/ Agent Note to track where its existing decision lives is required, not forbidden; see implemented/AGENTS.md.

An implemented Agent Note that is fully superseded may be consolidated into the current owning note and deleted. Before deletion, the owner must preserve every unique rationale, alternative, consequence, verification contract, and named coverage gap; repair every inbound link; and delete the Chinese counterpart and consistency record in the same change. Partial supersession does not qualify: keep both notes cross-linked and update every fact that remains current. Consolidation must not rewrite the old file into its opposite or rely on git history as the only copy of rationale.

A feature-addition note may be consolidated into the later removal note only when the feature is absent from production code, configuration, schemas, durable or wire formats, migration, and compatibility behavior; no current documentation presents it as available; and no test exercises it as supported behavior. Removal rationale and tests that verify absence may remain. The removal owner preserves the original motivation, why it no longer justified the feature, alternatives to full removal, the capability given up, conditions for reintroduction, and verification of complete absence. Obsolete implementation inventories and tests that only verified the deleted behavior are not current verification contracts. Removing one transport, default, implementation, or presentation is partial supersession, as is any surviving durable data or compatibility handling.

The file format

Every Agent Note follows one in-file format, enforced by pnpm run verify-agent-note-format (scripts/verify-agent-note-format.ts, part of doc-sync); the rationale for the format — and the alternatives it rejected — is the uniform-format Agent Note.

The header block

The first three lines of every Agent Note are exactly:

# Agent Note: <title>

Status: <status>

followed by a blank line. The Status: value is one of three forms, and must agree with the lifecycle folder the file sits in — the gate cross-checks them:

  • Status: proposed
  • Status: implemented
  • Status: rejected — <why, in one line>

The status carries no dates and no parentheticals: the filename holds the first-proposed date, git holds everything else, and an "accepted in amended form" note is body content (state the amendment where the decision is stated). The rejection reason is the one status with content, because a rejected Agent Note's verdict is the fact readers come for.

The body skeleton

Every Agent Note opens its body with ## Problem — the motivation, written to stand without the solution. What follows depends on the lifecycle; recurring sections use these canonical names and nothing else, while genuinely bespoke technical sections (package topology, wire contracts, schemas) remain free-form between the required ones.

proposed/

## Problem
## Proposal
…bespoke sections…
## Alternatives considered
## Acceptance criteria
## Risks

## Proposal is the intended change and may legitimately speak in the future tense — plans, migration steps, and open questions belong here while the work is unbuilt. ## Acceptance criteria says what observable state means done. ## Risks covers both what could go wrong and what the change knowingly gives up.

implemented/

## Problem
## Decision
…bespoke sections…
## Alternatives considered
## Consequences

## Decision describes shipped reality in the present tense, and the whole file is kept current with it per implemented/AGENTS.md. ## Consequences records what the trade-off cost and bought. Proposal-era headings are spec-speak here and the gate rejects them: ## Proposal, ## Plan, ## Migration plan, and ## Acceptance criteria may not appear in an implemented Agent Note (the slop checklist names why). A ## Testing, ## Deferred, or ## Related section is fine where it states present-tense fact.

rejected/

A rejected Agent Note is the proposal, frozen: it keeps whatever proposal-time sections it had (including ## Acceptance criteria or ## Plan), and the verdict lives on the Status: line. Only the header block, the ## Problem opener, a ## Proposal section, and the Alternatives-considered mandate below apply.

Alternatives considered — mandatory

Every Agent Note carries an ## Alternatives considered section: each genuine alternative and why it lost, one bold-led paragraph per alternative or a ### Why not <X>? subsection per contested one. A decision recorded without what it beat invites re-litigation — the failure Agent Notes exist to prevent.

Alternatives are recorded, never invented. An Agent Note dated before 2026-07-05 whose alternatives are not reconstructible from the record carries this exact comment in place of the section, which the gate accepts for pre-format files only:

<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->

Moving between lifecycles

Moving a file between lifecycle folders means updating the Status: line and re-satisfying that folder's skeleton in the same change — the gate fails the move otherwise. Concretely, proposed/ → implemented/ rewrites ## Proposal into a present-tense ## Decision, folds ## Acceptance criteria and ## Risks into ## Consequences (or a present-tense ## Testing/## Verification section for what now pins the behavior), and drops plans in favor of what shipped — the rewrite implemented/AGENTS.md requires, made mechanical. proposed/ → rejected/ only adds the reason to the Status: line and freezes the file.

Chinese counterparts

A .zh.md counterpart mirrors its English sibling's structure section-for-section under the i18n contract; the machine-checked header tokens (# Agent Note: and the Status: line) stay in English verbatim. The format gate skips .zh.md files — the pairing gate owns their consistency.