chore(public): remove private development and legacy files

This commit is contained in:
zhoutianyi
2026-09-03 00:55:38 +08:00
parent 33e68685f6
commit efc34695fd
228 changed files with 205 additions and 25689 deletions
-7
View File
@@ -1,7 +0,0 @@
# AGENTS.md — Agent Notes
Follow [README.md](README.md) and the [documentation standard](../../docs/AGENTS.md).
Every new note needs a supersession search of the active tree. Archive or cross-link instead of duplicating a decision.
Do not treat notes as the current API. Standing behavior lives in [docs/architecture.md](../../docs/architecture.md).
-1
View File
@@ -1 +0,0 @@
AGENTS.md
-63
View File
@@ -1,63 +0,0 @@
# Agent Notes
An Agent Note records a decision that code and standing docs cannot carry: the why, what we gave up, and how to verify it.
## Path
`{lifecycle}/{class}/yyyy-mm-dd-topic-title.md`
Lifecycle:
- `proposed/` — not built, or only partly built. Future tense is allowed.
- `implemented/` — shipped. Present tense. Keep paths and names current in the same change that moves them.
- `rejected/` — declined. Keep only while the reason still prevents a real mistake.
Class (closed set): `feature`, `bug-fix`, `simplification`, `architecture`, `process`, `testing`.
Architecture is about product and source structure; its lifecycle says whether that structure is proposed or shipped. Process is tooling and workflow around the source.
There is no `INDEX.md`. Browse the folders.
## When to write one
Every reviewable feature, bug fix, simplification, testing change, or governance change gets one concise dedicated Note in the same feature commit. “One home” means that the slice has an explicit owner; it does not mean appending unrelated decisions to a long-running aggregate Note. Aggregate Notes retain overall direction and status and link to feature Notes instead of repeating their rationale.
For a governed feature range, the diff gate requires either a newly added dedicated Note or the lifecycle rename of the proposal that already belongs to that feature. Merely modifying an existing Note does not satisfy ownership. The gate deliberately uses paths instead of pretending it can infer feature semantics; the feature-commit rule and semantic review still reject a range that combines independent slices behind one Note. Governed paths are `packages/`, `src/`, `tools/`, `scripts/`, `prompts/`, `plugins/`, `.githooks/`, `.agents/skills/`, `.github/`, `docs/cookbook/`, `docs/design/`, `docs/process/`, the live architecture/development/testing docs, every local `AGENTS.md`/`CLAUDE.md`, and root instruction, skill, dependency, package-manager, TypeScript, ESLint, Vitest, Knip, and Prettier configuration files.
Tests-only changes — including co-located `*.test.ts` / `*.spec.ts` files and their `__snapshots__/` output — translations, assets, and local-only work do not trigger the diff gate. A semantic reviewer still requires a Note if an exempt path changes behavior, architecture, a shared contract, process, testing strategy, or an on-disk / wire / config format.
While a feature is still being assembled, update its dedicated Note. A later independently reviewable change gets a new Note and cross-links any earlier owner it supersedes or extends. Do not rewrite an implemented Note into the opposite conclusion.
Search the tree for supersession before adding a Note, but do not use that search as a reason to turn an aggregate Note into a development log. Agent Notes record feature boundary, decisions and reasons, rejected alternatives, risks and invariants, verification, and unverified scope. They do not copy the canonical specification or narrate task progress.
## File format
Lines 1–4:
```markdown
# Agent Note: <title>
Status: proposed
```
`Status:` must match the folder: `proposed`, `implemented`, or `rejected — <one-line reason>`.
Then `## Problem` first.
### proposed/
`## Problem` `## Proposal` `## Alternatives considered` `## Acceptance criteria` `## Risks`
### implemented/
`## Problem` `## Decision` `## Alternatives considered` `## Consequences` `## Verification`
Banned headings: `## Proposal`, `## Plan`, `## Migration plan`, `## Acceptance criteria`.
### rejected/
Keeps proposal-era sections. The verdict is the `Status:` line.
`## Alternatives considered` is required. Each real alternative and why it lost.
`python3 -B scripts/verify_agent_notes.py` enforces the tree and format. Before a feature push or PR, `python3 -B scripts/verify_agent_notes.py --base <resolved-base-sha> --head <exact-head-sha> --range-mode merge-base` also enforces governed-diff ownership. Direct persistent-branch push events use `--range-mode direct` so a rewind cannot disappear behind a merge-base diff. English-only notes are enough on this branch; a later pairing gate may require `.zh.md`.
-7
View File
@@ -1,7 +0,0 @@
# AGENTS.md — Implemented Agent Notes
These notes describe shipped decisions in the present tense.
Keep paths, symbols, and defaults current in the same change that alters them. Rewrite stale facts in place. Do not append a changelog.
Reversing the decision requires a new note and a cross-link.
-1
View File
@@ -1 +0,0 @@
AGENTS.md
@@ -1,53 +0,0 @@
# Agent Note: SQLite create and ingest landing reconciliation
Status: implemented
## Problem
The storage landing order contradicted itself. Subject identity deferred standalone `subjects.create` while the first SQLite foundation required it beside `materials.ingest`; deleting the old queue in the same slice would also have pulled pending reads and leases into the wrong feature. Shared file stores and locks still support unreplaced package-internal tests, but treating them as a live compatibility path would create dual authority.
The ingest contract also excluded `title` and `capturedAt` from MaterialId while describing every source-field difference under an existing MaterialId as corruption. A later retrieval can legitimately change those display-only values without changing the frozen material identity.
## Decision
The first SQLite business-method slice implements standalone `subjects.create` and `materials.ingest(existing|create)` over one root-scoped `node:sqlite` WAL store. Both paths share a package-private transaction-local identity primitive; create-ingest never calls the standalone public-style service and atomically commits the subject, first material membership, pending marker, stable operation result, and events.
Normalized material bodies are published to the immutable SHA-256 blob store before the short SQL transaction. One mutation-scoped shared access lease covers the complete reference snapshot, every blob access, and the SQL commit or rollback; it is released before post-commit observers. This prevents a queued maintenance writer from splitting a multi-blob mutation. The snapshot distinguishes an existing blob authority row from dependent material references: a dependent without its authority parent fails closed. A digest with an existing authority row is verified read-only and fails closed if its physical blob is missing or inconsistent; commit also refuses to recreate a referenced parent that disappeared after the snapshot. Only a digest with neither authority nor dependent reference may use no-replace CAS. A failed mutation may leave an unreferenced immutable blob for the later generic GC, but it leaves no visible subject, material reference, pending row, operation, or event.
The SQLite composition has no transitive file-store, business-lock, journal, recovery, staging, queue, or projection dependency. The live ingest transaction record, staging tree, ingest recovery branch, and space catalog/identity locks are removed. Shared file mechanisms needed by unmigrated brief/commit/review tests are reachable only through `legacy-file-engine.test.fixture.ts`; they neither mirror nor adapt the SQLite path.
Material rows retain first-seen `title`, `capturedAt`, and engine `storedAt`. Later-only differences in those fields classify as a duplicate without rewriting the row. Content, provenance, source identity, and all other identity-bearing semantics must remain exact, and stored material facts are checksum-verified before use.
Storage preflight uses one read-only SQLite snapshot so `user_version` and `sqlite_schema` cannot straddle another process's initialization commit. The bounded, connection-local busy timeout applies before that snapshot; persistent permission, WAL, foreign-key, and synchronous configuration still waits until schema trust is established. Existing schema v1 is verified against the exact Distilly shape before permissions or journal mode are changed; unversioned unknown data and future versions are likewise rejected without mutation. Schema v1 has no versions authority, so both version pointers are constrained to NULL and verified reads reject bypassed dangling values.
Exact locators are checked globally before resolving the requested space; locators that independently identify different healthy subjects produce a stable `ambiguous_subject`, while multiple owners or a missing locator parent remain storage corruption. Same-label aliases inspect only their owning space before a complete subject read: a missing subject or space parent fails closed, a healthy different-space owner is outside the targeted read, and only same-space owners participate in conflict resolution. A missing built-in or explicitly requested space is not recreated or reported absent when existing subjects still reference it. Each event is transactionally bound to the same RequestId operation and must match its canonical actor and subject scope.
The root build removes only each direct TypeScript-reference package's real `lib/` directory before a forced build. The Engine dry-run package gate rejects test support, fixtures, and the retired create/ingest artifacts while allowing file modules that still belong to later migrations. This makes removal of compiled legacy artifacts reproducible rather than dependent on a developer's previous build tree.
This implements the first vertical part of the [single-writer SQLite authority decision](../simplification/2026-08-21-single-writer-sqlite-storage-authority.md). It does not expose a complete Engine factory or claim that the later method migrations are complete.
## Alternatives considered
- Deferring standalone create was rejected because it would leave a declared mutation unimplemented after its shared identity transaction already existed.
- Calling standalone create before ingest was rejected because two mutations can expose an empty subject and cannot atomically store the first generation, operation, and events.
- Deleting the legacy queue here was rejected because pending reads and lease mutations own that migration; retaining it as a live adapter or dual-write target was also rejected.
- Treating changed display-only metadata as corruption was rejected because those fields intentionally do not participate in MaterialId. Updating the old row was rejected because immutable first-seen provenance must not drift.
- Recreating a missing referenced blob and then reporting corruption was rejected because a concurrent caller could observe the temporary repair and succeed. Referenced content therefore has a verify-only path.
- Taking a separate blob lease per digest was rejected because writer priority lets queued maintenance deadlock a multi-blob mutation between puts. Removing writer priority was rejected because it can starve maintenance.
- Adding an outbox consumer, projection, doctor, backup, restore, or GC task abstraction was rejected because none has a consumer in this vertical slice.
## Consequences
SQLite is authoritative only for the package-private create/ingest composition until later feature commits migrate the remaining methods. The package root stays empty, the old brief/commit/review code has no production composition, and the disposable legacy queue remains test-only until brief/lease migration deletes it. The version migration must advance the private schema before it can store current or suspended pointers.
Exact RequestId retries return the stored result without duplicate rows or events; changed input, actor, or method conflicts globally. WAL plus one business transaction replaces create/ingest mutation-specific crash recovery. Schema v1 is deliberately limited to the identity, material reference, pending, operation, and event relations consumed by this slice.
## Verification
- `pnpm exec vitest run packages/engine/src/storage/sqlite-engine-store.test.ts packages/engine/src/storage/content-addressed-blob-store.test.ts packages/engine/src/storage/mutation-ledger.test.ts packages/engine/src/subject/service.sqlite.test.ts packages/engine/src/ingest/service.test.ts packages/engine/src/ingest/composition.boundary.test.ts packages/engine/src/facts/transaction-store.test.ts` passes 54 tests on Node 22.
- `pnpm exec vitest run packages/engine/src packages/protocol/src` completed 53 of 54 files and 742 of 743 tests; its sole failure was the unmigrated file-journal commit-recovery case exceeding 45 seconds under the full suite's filesystem load. That exact case passed alone in 6.97 seconds with `--maxWorkers=1`, so no assertion or SQLite composition regression remained.
- `pnpm run build` followed by `pnpm --filter @distilly/engine run smoke:built` reopens committed storage, races two real create-ingest processes, and uses real `SIGKILL` after blob publication, immediately before COMMIT, and immediately after COMMIT. Pre-COMMIT crash cases verify the exact content-addressed path is a regular file with byte-identical content before retrying.
- `pnpm run gates:fast`, `pnpm run typecheck`, the Protocol snapshot check, package-boundary tests, documentation verification, and Agent Note verification pass.
- The reproducible clean build plus `pnpm run check:engine-pack` verifies 302 dry-run Engine package files and contains no retired ingest staging, space lock, legacy fixture, test-support, or obsolete child-script artifact.
Brief/lease, commit, review/rollback, correction, verified read, projection, doctor, GC, backup/restore, runtime, binding, CLI, Panel composition, and Plugin acceptance remain unverified by this feature and are owned by later slices.
@@ -1,48 +0,0 @@
# Agent Note: SQLite brief and lease authority
Status: implemented
## Problem
SQLite already owns subject generations and pending jobs, but `distill.pending`, `distill.brief`, `distill.renew`, and `distill.release` still run through a sibling queue database, file state, request and subject locks, a lease journal, and mutation-specific recovery. That leaves one logical job split across two authorities and makes a crash require application-level reconciliation even though SQLite/WAL can commit the whole lease mutation atomically.
The first SQLite schema was committed only as unreleased local product work. Treating that private schema as published would create a compatibility promise before any runtime can have installed it. A complete briefing can also be several megabytes, so storing its idempotent operation result inline would duplicate private material text in the structured database instead of using the shared immutable blob store.
## Decision
Keep one canonical private schema v1 and fold the lease tables into that exact shape before any product runtime or installer exists. The schema version is a compatibility boundary, not a local implementation-step counter: new roots create the twelve-table v1 directly, a malformed or earlier local v1 shape fails exact verification without mutation, and a future `user_version` remains `schema_unsupported`. Canonical v1 adds one optional lease row per authoritative pending job and an operation-result blob reference table. Renew and release keep their small stable results inline. Brief stores a complete canonical template in the blob store and a small operation envelope that binds its RequestId, input checksum, subject, template pointer, and exact final lease.
Replace the live lease service with a SQLite implementation. Pending reads derive `pending` or `leased` from the optional lease and the caller's clock without expiry writes. Briefing loads and verifies the complete first-version material set under one shared blob-access lease, pins the prompt contract, builds a complete template with fixed-width placeholder lease fields, and passes fixed-point capacity checks before its write transaction. The transaction replays or conflicts on RequestId, rechecks the exact job generation and material-set snapshot, takes a fresh `acquiredAt`, grants the full 30-minute lease, overlays only fixed-width lease fields, atomically replaces only an absent or expired lease, and writes the operation and one `job.changed` event. The template and final result have the same canonical byte length, so the pre-transaction capacity result remains exact. Renew and release perform the equivalent active-owner compare-and-set in one transaction.
Include trusted actor, lease owner, and, for brief, the exact trusted capacity in the idempotency preimage. Exact replay short-circuits current job, expiry, prompt, and capacity state. Duplicate ingest must preserve an active lease when the material generation is unchanged; a new generation replaces the pending job and cascades the old lease.
Keep any still-needed file-backed lease implementation only as an explicit test fixture for the not-yet-migrated commit and review slices. The SQLite composition must have no transitive dependency on the sibling queue, lease transaction journal, file locks, or recovery service.
## Alternatives considered
- Keep the sibling queue as a projection and dual-write its lease marker. Rejected because pending job and lease would still have two writable authorities and two crash protocols.
- Bump the still-private schema from v1 to v2 or migrate the earlier local v1 shape. Rejected because no runtime or installer has shipped either shape; treating internal commit history as a disk-compatibility boundary would create migration obligations without an installed population.
- Store `HostDistillBriefing` inline in `operations.result_json`. Rejected because the result contains complete private material text and can approach the 4 MiB briefing ceiling; the blob store is the designated authority for large immutable result payloads.
- Fix `acquiredAt` before publishing the complete result blob. Rejected because prompt, serialization, Blob I/O, or scheduler delay would consume the caller's lease before SQLite had claimed the job. The fixed-width template plus transaction-owned lease envelope preserves both pre-transaction Blob I/O and a full post-acquisition lifetime.
- Reattach the legacy version store so incremental briefings work in this slice. Rejected because SQLite has no version authority yet and a dual read would make a briefing span incompatible storage systems. The reachable schema-v1 path remains first-version, no-baseline briefing until the commit/version slice lands.
- Delete expired lease rows during reads or on a timer. Rejected because expiry is a clock-derived view; a read must not become an unjournaled mutation or emit a synthetic expiry event.
## Consequences
SQLite is now authoritative for package-private create, ingest, pending, brief, renew, and release. New roots use the exact twelve-table canonical schema v1; malformed local v1 shapes and future versions fail without permission, journal-mode, or schema mutation. The schema still has no version, claim, or baseline membership authority, so the live briefing path is deliberately first-version only and rejects unexpected current, suspended, or base-version state.
Pending reads filter before limit, order by `queuedAt` and binary `JobId`, and derive lease expiry without writes. Schema v1 has no failed-job authority, so `state="failed"` returns an empty page instead of widening to all jobs. Duplicate ingest preserves the same-generation lease, while a new generation replaces the pending row and cascades its old lease.
Brief, renew, and release each own one SQLite transaction for lease, operation, and event state. Brief acquisition time is read inside that transaction and expiry is exactly 30 minutes later even when material preparation or template publication is delayed. Exact brief replay verifies the template and envelope without consulting the current pending row, prompt, expiry, or capacity. Renew preserves lease identity, owner, acquisition time, generation, and contract; release deletes only the active owned lease.
A losing brief race or pre-COMMIT crash can leave an unreferenced immutable template file. It is invisible because no SQLite authority references it, and the future generic GC owns cleanup rather than a brief-specific journal. The remaining file-backed commit, read, and review pipeline is reachable only through explicitly named test fixtures. Protocol journal/recovery types remain until their owning migrations complete, as required by design step 10; they do not restore a production legacy path.
## Verification
- Node 22.23.1: `pnpm exec vitest run packages/engine/src/distill/lease-service.sqlite.test.ts packages/engine/src/distill/lease-service.sqlite.crash.test.ts packages/engine/src/storage/mutation-ledger.test.ts` passes 3 files and 31 tests.
- Node 22.23.1: `pnpm exec vitest run packages/engine/src` passes 45 files and 501 tests, including the relocated legacy fixtures and real brief `SIGKILL` cases.
- `pnpm run build`, `pnpm run typecheck`, and `pnpm run gates:fast` pass after a clean build.
- Package-boundary tests pass 36 cases; build-artifact tests pass 19 cases; `pnpm run hygiene` passes built smokes, Knip, plugin assembly, the 282-file Engine dry-run pack check, publint, and `attw`.
- `python3 -B scripts/verify_docs.py`, `python3 -B scripts/verify_agent_notes.py`, and `git diff --check` pass.
Node 24 is not installed in this local environment; the existing Node 22/24 CI matrix owns that second runtime lane. Commit/version authority, correction, production verified reads and projections, doctor/GC/backup, runtime, host composition, CLI, Panel composition, migration, and installable Plugin acceptance remain unverified by this feature and belong to later slices.
@@ -1,30 +0,0 @@
# Agent Note: Model-visible runtime gate
Status: implemented
## Problem
The canonical Distilly Skill required a trusted `HostPreflight` result to be visible to the model before it could call any Distilly tool. Codex correctly keeps that result inside binding and runtime composition, so a healthy installation exposed all five MCP tools but the Skill still instructed the model to stop. This made the installed Preview unusable despite a successful internal preflight.
## Decision
The binding completes trusted preflight before starting MCP and keeps the accepted capacity bound to the runtime session. The model-facing gate checks only that the exact five Distilly tools are present. A missing tool or a real host-capability, handshake, or runtime error still fails closed. The Skill also gives the exact initial resolve envelope, including the top-level wire/request fields and nested subject query, after a fresh-host probe showed that prose-only request-id guidance could still produce an invalid first call. Optional source capabilities such as web, local-file conversion, OCR, or transcription are used only when the current session exposes a suitable tool or input path; five Distilly tools do not imply those capabilities.
The canonical Skill and generated Codex and Claude Code mirrors carry this rule. Because the immutable Codex host-capacity record is release-bound to the canonical Skill digest, the real-host evidence is regenerated for the corrected digest rather than bypassed.
## Alternatives considered
- Expose `HostPreflight` as a sixth MCP tool. Rejected because the public contract intentionally has exactly five tools and preflight is trusted binding/runtime state, not a model action.
- Ask the user to paste or confirm preflight data. Rejected because user-provided capability claims are not trusted evidence and would expose an internal implementation detail.
- Remove runtime gating entirely. Rejected because missing tools and genuine handshake or capacity failures must remain visible and fail closed.
## Consequences
Fresh Codex and Claude Code sessions can begin the workflow when the five tools are available without inventing an inaccessible prerequisite. Runtime safety remains enforced below the prompt boundary. Source acquisition remains conservative: unavailable conversion or research capabilities require a traceable textual fallback.
## Verification
- `python3 -B scripts/assemble_plugins.py --check`, `python3 -B -m unittest tests.test_assemble_plugins`, and the canonical Skill validator pass.
- Real Codex 0.146.0 capacity verification passes at the exact 16,384-byte briefing and 65,536-byte tool-result boundaries for the corrected Skill digest; the focused fixture test passes.
- `pnpm run package:preview:codex` and the real-host packaged Codex verifier pass in an isolated home, including exact five-tool discovery, the product flow, person-Skill installation, and data-preserving uninstall.
- The corrected package is reinstalled locally with unchanged SQLite bytes. `codex exec --ephemeral --sandbox read-only --json` with the installed `$distilly` Skill calls `distilly_get` once with the exact resolve envelope and returns `not_found` for a unique probe, without asking for `HostPreflight` or modifying person data.
@@ -1,34 +0,0 @@
# Agent Note: Real Codex use blockers
Status: implemented
## Problem
A fresh real Codex run could complete the Preview workflow only after several invalid calls and direct inspection of the bundled runtime schema. The Protocol already exposed strict MCP input schemas, but the canonical Skill gave a complete example only for subject resolution. The model consequently confused the different subject selectors, invented ingest provenance fields, supplied a non-canonical timestamp, mis-shaped an id-based profile read, and later inspected packaged code to discover the first-version claim operation.
The rebuilt package then exposed a separate fresh-host startup failure. Codex removes `CODEX_HOME` from the environment it gives an MCP child. Distilly's strict installed-host version recheck restored only `HOME` and `USERPROFILE`, so the nested Codex version command emitted a temporary-directory warning on stderr and the MCP correctly failed closed before initialize.
## Decision
Keep the Protocol unchanged and add only the model-facing redundancies proven necessary by the real run: exact selector boundaries, one complete local-text ingest template, canonical UTC-millisecond timestamp guidance, direct briefing-to-commit field mappings, one complete first-version `add` template, the evidence-reference shape, an id-based read template, and the correction envelope. The canonical shared Skill remains the source; release assembly mirrors it byte-for-byte into both host plugins.
Keep strict host-version parsing unchanged. Every Codex version probe now explicitly restores the installed home as `CODEX_HOME`, including setup, doctor, and host-spawned MCP composition. It also places the manifest-verified Distilly Node directory first on `PATH`, so a script launcher with `#!/usr/bin/env node` cannot select Codex's temporary arg-zero helper path. The manifest's already verified absolute executable and stored version remain authoritative inputs; no ambient Codex home is trusted.
## Alternatives considered
- Relaxing the strict Protocol schemas was rejected because the rejected fields and timestamps were genuinely ambiguous or non-canonical, and accepting them would weaken the wire contract.
- Copying every MCP schema into the Skill was rejected because it would duplicate the Protocol and obscure the workflow. Only mistakes observed during a real model run and cross-call mappings that JSON Schema cannot express are repeated.
- Treating eventual recovery after runtime-source inspection as successful usability was rejected because a packaged user flow must work from the published Skill and tool schemas alone.
- Allowing arbitrary version-probe stderr was rejected because it would weaken fail-closed host identity checks for warnings unrelated to this known environment omission.
- Skipping the host version recheck inside MCP was rejected because the installed executable can change after setup and the release-bound capacity evidence is exact-version only.
## Consequences
The canonical Skill is slightly longer, but a fresh host model has concrete valid shapes at each previously failing boundary. Changing the Skill bytes also changes the canonical release digest, so host-capacity evidence must be regenerated and rebound before the package can install fail-closed. Codex-spawned MCP startup no longer depends on whether the host preserves `CODEX_HOME`, while unexpected version stderr still stops startup.
## Verification
- Assemble and validate byte-identical Codex and Claude Code Skill mirrors.
- Run the narrow Skill/release, host-capacity, CLI build, and packaged verifier checks.
- Install the rebuilt package into an empty isolated home and complete resolve, ingest, brief, commit, verified profile, prompt read, and correction without `invalid_input` or runtime-source inspection.
- Exercise Panel review, long-term person-Skill discovery in a fresh Codex process, and Plugin uninstall while preserving person data.
@@ -1,27 +0,0 @@
# Agent Note: Remove the artificial Codex briefing cap
Status: implemented
## Problem
The Codex Preview capacity fixture used a 16,384-byte complete briefing because that was the first conservative observation chosen for the host harness. The harness proved that value survived the real structured/text MCP path and reached the model, but it did not prove that the next byte failed. Treating the sampled value as the product maximum blocked a 30,496-byte first-profile briefing even though the same host path already carried a 65,536-byte tool result.
## Decision
Use 65,536 bytes as both the verified complete-briefing budget and tool-result budget for the exact Codex CLI 0.146.0, release, wire, Skill, descriptor, and serializer tuple. The real-host harness fills each exact-size payload with deterministic high-entropy text, places different unseen markers at the beginning, multiple middle positions, and the end, and requires the model to return all markers in order. A binding fixture advertises an exact net budget proven complete at that value; it does not claim to have discovered the host's true failure threshold. The Engine continues to require a complete briefing and never truncates materials.
## Alternatives considered
- Remove every capacity check. Rejected because an unverified host truncation could produce a Profile that silently omitted evidence.
- Keep 16,384 and ask users to shorten supplied material. Rejected because that number was a proven safe sample, not a measured host failure boundary, and it blocked an ordinary 30,496-byte briefing.
- Add briefing pagination or map-reduce in this fix. Deferred because it requires a new completeness proof and protocol shape; it remains the correct separate solution for inputs beyond the verified transport budget.
## Consequences
The current 30,496-byte case passes without user intervention, and the Preview accepts complete briefings up to the verified 65,536-byte budget. The generic 4 MiB Engine ceiling, 999-reference bound, exact host-tuple matching, and fail-closed no-truncation behavior remain unchanged. This changes no MCP tool, wire schema, canonical Skill byte, storage format, or Panel behavior.
## Verification
- A real Codex 0.146.0 run observed the exact 65,536-byte high-entropy briefing and tool result, preserved structured/text equality, and returned every distributed unseen marker from both payloads.
- The immutable fixture test asserts both advertised net budgets are exactly 65,536 bytes.
- The self-contained Codex package verifier covers setup, exact five-tool discovery, the local profile flow, correction and review, person-Skill persistence, and uninstall data preservation with the regenerated evidence.
@@ -1,32 +0,0 @@
# Agent Note: Developer Preview lifecycle and intake safety
Status: implemented
## Problem
The Codex setup path probed a fresh host before creating its ordinary `.codex` home, so a clean user home failed before the Plugin could be installed. Uninstall also treated a missing owned plugin tree as an unowned tree and left the host registration behind. Local file intake followed selected file symlinks, used filesystem modification time as capture provenance, and accepted duplicate basenames that could collapse the fallback source identity. Profile prompts had no session-bound capacity check even though the first version must be delivered complete.
## Decision
Codex setup creates only the regular `.codex` directory required by its version probe and rejects unsafe lifecycle paths through the existing directory guard. Uninstall distinguishes an absent owned tree from a present tree without Distilly ownership: an absent tree still has its verified host registration and marketplace entry removed, while a foreign or corrupt tree remains fail-closed.
The local loader uses `lstat` for the selected file, rejects a symlink at that path, verifies regular-file size and modification time after reading, and rejects duplicate basenames before any bytes are read. The Engine replaces loader-supplied capture timestamps with its trusted transaction clock for both raw and parsed source records. A connected session's complete profile prompt is checked against its trusted input and tool-result budgets using the same conservative UTF-8-byte upper bound as briefing; it is never truncated, and sessions without an advertised capacity retain direct SDK reads.
## Alternatives considered
- Creating `.codex` inside every generic host probe was rejected because doctor and uninstall must remain read-only with respect to missing host homes.
- Removing all marketplace entries whenever the plugin tree is missing was rejected because a present foreign or corrupt tree must not authorize destructive cleanup.
- Following symlinks and relying on `stat` was rejected because an explicitly selected path must not silently redirect to another file; rejecting every ancestor symlink was also rejected because macOS system paths such as `/var` legitimately use symlinked ancestors.
- Trusting file mtime as `capturedAt` was rejected because it describes filesystem history, not the acquisition event. Adding an arbitrary raw-file size ceiling was rejected because the parser's existing bounded output is the relevant product limit.
- Truncating a prompt or imposing a new fixed 16 KiB ceiling was rejected. The guard consumes only a verified session budget and returns `context_too_large` with numeric diagnostics when the complete prompt cannot fit.
## Consequences
Fresh Codex setup now reaches host preflight without a manual directory-creation workaround, and uninstall removes stale Distilly registration while preserving foreign installations and user data. Selected local symlink files and duplicate labels fail before subject creation or storage writes. Persisted source provenance is acquisition-time data owned by Engine rather than a caller-controlled mtime. Host-bound prompt calls fail visibly when their complete output exceeds the exact verified budget; no content is silently lost. The existing release skill bytes remain unchanged, so the immutable host-capacity fixture remains valid.
## Verification
- Node 22: `pnpm run build` passes.
- Node 22: `pnpm exec vitest run packages/cli/src/lifecycle.test.ts packages/bindings/src/full-binding.test.ts packages/runtime/src/preview.test.ts packages/engine/src/preview.test.ts packages/engine/src/distill/prompt-capacity.test.ts packages/protocol/src/mcp.test.ts --maxWorkers=1` passes 6 files and 52 tests.
- The tests cover fresh Codex homes, stale plugin registration cleanup, final-file symlink rejection, duplicate basename rejection, acquisition-time provenance, complete prompt capacity boundaries, and the UTF-8 correction boundary.
- `python3 -B scripts/assemble_plugins.py --check` verifies that the canonical skill, host mirrors, and release manifest remain byte-consistent.
@@ -1,34 +0,0 @@
# Agent Note: Step 10 verified review and injected Panel
Status: implemented
## Problem
Step 10 crosses immutable facts, recovery, a disposable Library projection, and a browser transport. Without one feature boundary, a staged or orphaned version could become readable, a review decision could survive only partly, a clean Library could lag committed facts, or the local Panel could exercise engine authority without a verifiable user action.
## Decision
The feature is the package-internal verified read/review slice plus the injected-client `@distilly/panel` leaf. Canonical rules remain in [Design V3](../../../../docs/design/system-v3.md).
- Verified reads reconcile, hold the subject lock through one snapshot, and require immutable files, materials, state, and exact lifecycle events to prove one connected current lineage with a unique leaf. Physical materials must equal the union referenced by current state and committed versions.
- Promote, reject, and rollback are idempotent journaled transactions with atomic state replacement as the commit point. Recovery accepts only an exact target or exact previous state and verifies the complete correlated event tuple. Promote and rollback rebase pending work; reject preserves it; rollback creates a new immutable descendant.
- Library remains disposable. A Library-changing writer durably records intent before fact mutation and holds its reservation through projection apply and journal terminalization. Only a clean terminal writer clears its exact token; dirty or interrupted work retains intent until reconciliation proves no prepared owner remains. Query reads only the validated projection; rebuild scans verified seeds under the Library lock without acquiring every subject lock.
- Panel borrows a complete `EngineClient`. Exact loopback Host, Origin, Bearer authentication, bounded HTTP/static responses, authenticated fetch-streamed SSE, CSP, and a single-use nonce bound to token, method, request, and canonical params constrain browser authority. Launcher lifecycle is single-flight, terminal on close, and never owns the borrowed client.
- The feature excludes production runtime composition, CLI/setup, Correction, archive/install, raw extraction, and production doctor behavior. The Panel exposes only implemented read and promote/reject/rollback paths.
## Alternatives considered
- Trusting every schema-valid artifact directory was rejected because publication precedes the state commit point and journal-less orphans are not committed facts.
- Pointer-only review and unjournaled rollback were rejected because crashes could split state, events, operations, projections, queue rebasing, and immutable output.
- Dirty marker plus projection lock alone was rejected because a crash between fact commit and dirty creation can leave a clean-stale index; durable pre-mutation intent closes that window.
- `EventSource`, browser confirmation alone, and partial runtime exports were rejected because they cannot respectively carry the required Bearer header, prove mutation scope, or honestly represent production handler availability.
## Consequences
Every new Library-changing writer must preserve request/identity/subject/Library lock order and intent settlement. Panel is a loopback single-user boundary, not a remote or multi-user service. A complete injected transport must not be described as production composition.
## Verification
Node 22 gates pass for 862 TypeScript tests, 46 Panel HTTP tests, the built Chromium path, package boundaries, Protocol/Engine/Facade/MCP/Bindings suites, typechecking, snapshots, builds, hygiene, Python tests, documentation generation, and Agent Note governance. Fixtures cover committed-version/material invariants, promote/reject/rollback replay and crash recovery, Library dirty/intent races, all Engine method RPC mappings, nonce rebinding and consumption, SSE lifecycle, review evidence, and Launcher races.
Production runtime/CLI/setup, Correction, raw extraction, real host installation, and fresh-install end-to-end behavior remain unverified because they are later features.
@@ -1,29 +0,0 @@
# Agent Note: Protocol storage parity
Status: implemented
## Problem
The in-force storage contract already distinguished stable privacy-purge results from live blob-GC health and kept whole-root backup/restore outside ordinary business methods. Protocol and the public Facade still exposed purge as a null result, omitted live GC diagnostics, and had no typed maintenance boundary, so the next SQLite slice could not implement the design without changing public contracts mid-feature.
## Decision
Protocol publishes a strict `PurgeResult` discriminated by `physicalDeletion`, with `pendingBlobCount` required only as a safe positive integer on the pending branch. `DoctorSnapshot.storage.pendingBlobGcCount` is a safe non-negative live count. `Distilly.purge` returns the complete stable result instead of discarding it.
Whole-root backup and restore use `EngineAdministrationClient` plus a separate `engineAdministrationSchemas` registry. The registry validates strict input/result objects but remains outside the exact 35-key `EngineMethodMap`, the Panel RPC surface, and the five MCP tools. The design's `ReviewService.reject` signature now matches the already-shipped version method and service result: the rejected `VersionSummary`.
## Alternatives considered
- Keeping purge as `null` until SQLite landed was rejected because it would force the storage implementation and its callers to land against a knowingly stale public contract.
- Adding backup and restore to `EngineMethodMap` was rejected because whole-authority maintenance freezes or replaces the root and is not an idempotent subject business mutation.
- Reusing the original pending purge result as live GC status was rejected because RequestId replay must remain stable after background physical deletion completes.
## Consequences
Every complete EngineClient fixture now supplies deletion status and the doctor GC count. Future storage code must persist the original `PurgeResult` for replay while computing doctor health from current GC state. Runtime and CLI may borrow the administration client from the single root owner; ordinary SDK, Panel, and MCP clients cannot call it.
## Verification
Protocol type/runtime tests cover both purge branches, positive/non-negative integer boundaries, strict administration schemas, exact method-registry separation, operation-result correlation, and explicit public exports. Facade, MCP fake, Panel transport fixtures, built smoke, typecheck, snapshots, documentation generation, and Agent Note governance pass on Node 22.
SQLite purge, GC, backup, restore, runtime composition, and CLI commands remain unimplemented and therefore unverified; this feature ships their shared contracts only.
@@ -1,41 +0,0 @@
# Agent Note: Codex Panel vertical Preview
Status: implemented
## Problem
The Codex MCP composition returned a real loopback ReviewLaunch and injected a separate direct-user EngineClient into the Panel, but its product path was not yet a reliable vertical Preview. Review-route recovery required the deferred deep Doctor and therefore turned a successful promote or reject into a Panel error. The CLI also selected and released a Panel port when MCP started even though the Panel might not bind until much later. Finally, the checked-in browser bundle was stale and the built CLI smoke stopped at `/health`, so no test proved that the real SQLite authority could be reviewed through the browser UI.
## Decision
The Codex ReviewLaunch remains the only Preview Panel entry. The Runtime tags its intentionally deferred Doctor error with `details.kind=preview_method_deferred` and `details.method=system.doctor`; Panel-wide recovery tolerates only that tagged, non-retryable `schema_unsupported` result, and Settings renders the limitation as read-only text. Every unrelated Doctor failure still surfaces. Automatic port selection now occurs inside each lazy Panel start attempt; tests may still supply a fixed port. The built Codex MCP smoke drives Chromium through the existing token, nonce, RPC, and UI paths against one real LocalRuntime/SQLite root to promote one correction, reject another, roll back to the initial historical version, verify every result through MCP, and verify the result again after reopening.
## Alternatives considered
- Adding `distilly panel` was rejected because the suspended-result URL is already the required Codex entry and the current design does not enable that command in this Preview.
- Implementing deep Doctor was rejected because it is explicitly deferred and is not required for review.
- Replacing the existing Panel with a second minimal UI was rejected because the injected server/web implementation already owns the reviewed security and mutation paths.
- Keeping only HTTP assertions was rejected because the remaining risk was the composition between the built browser bundle and the real backend; existing HTTP and fake-browser suites already cover their isolated contracts.
## Consequences
Codex users can follow a correction ReviewLaunch into the real local Panel, inspect Library, Subject, evidence, versions, and active reviews, then promote, reject, or roll back against the same SQLite authority used by MCP. Review recovery stays usable while deep Doctor remains deferred, without pretending Doctor succeeded. The checked-in browser bundle is regenerated from the current Protocol and UI source.
Automatic port selection still has the normal short free-port probe-to-bind race. Because allocation occurs inside each lazy start attempt and PanelLauncher returns to `new` after a start failure, a repeated review action can select a new port instead of being pinned to a stale MCP-startup choice. Eliminating even that short race requires a separately designed internal port-zero server seam and remains post-Preview work.
## Verification
- `pnpm exec vitest run packages/runtime/src/preview.test.ts packages/panel/src/web-recovery.test.ts packages/cli/src/preview.test.ts`
- `pnpm exec vitest run packages/cli/src packages/panel/src/web-recovery.test.ts`
- `pnpm --filter @distilly/panel run typecheck`
- `pnpm --filter @distilly/panel run build:web`
- `pnpm --filter @distilly/panel run snapshots`
- `pnpm run test:panel-http`
- `pnpm run test:panel-browser`
- `pnpm --filter @distilly/cli run smoke:built`
- `pnpm run gates:fast`
- `pnpm run typecheck`
- `pnpm run build`
- `pnpm run hygiene`
- `python3 -B scripts/verify_docs.py`
- `python3 -B scripts/verify_agent_notes.py`
@@ -1,37 +0,0 @@
# Agent Note: Codex person Profile projections
Status: implemented
## Problem
The Codex Preview could install and launch the five-tool Plugin, and the public `Person` facade already exposed temporary `get` / `prompt` plus explicit `install` / `export` / `uninstall` methods. The LocalRuntime still rejected all three host mutations, however, and the MCP entry constructed only a capability binding. A committed Profile therefore could not reach the existing self-contained Codex person-Skill projector through the product path, and host mutation RequestIds had no SQLite replay authority.
## Decision
The Engine Preview now owns a narrow authority for the three host-mutation operation records while filesystem projection remains in the full Codex binding. Runtime composes that full binding, resolves current or explicitly pinned Profiles through verified Engine reads, invokes only its binding-created injector, and finalizes the exact result in the SQLite operation ledger. The outer CLI reconstructs the verified full Codex binding from its owned install record and passes the binding plus trusted host context into the MCP application.
This slice accepts one full host projection because the active Preview command is host-specific. Missing or mismatched bindings fail closed, and the public `Person` and Protocol contracts remain unchanged. Uninstall verifies that its exact `InstallRef` came from a canonical prior install result before the binding can touch the filesystem.
## Alternatives considered
- Returning the existing injector result directly from Runtime was rejected because it would lose global RequestId replay and conflict semantics.
- Letting Runtime open or write SQLite was rejected because Engine remains the only structured-storage writer.
- Passing a bare injector from CLI into Runtime was rejected because the in-force target graph makes Runtime the binding composition owner; the CLI supplies a verified full binding and trusted host context instead.
- Adding an installation catalog, crash-orphan collector, or multi-host registry in this slice was rejected because none is required for the Codex vertical Preview flow.
- Calling a live model to prove discovery was rejected because Codex exposes a local prompt-input diagnostic. A fresh Codex process verifies that the installed person Skill is model-visible without authentication or model API usage.
## Consequences
- A committed Profile supports `Person.get`, `Person.prompt`, and explicit Codex install/export/uninstall through LocalRuntime.
- Install writes only a self-contained `SKILL.md` and digest ownership manifest under the canonical Codex skills root; raw materials and private source paths are absent.
- Exact RequestId replay returns the stored result without repeating a completed projection, while conflicting reuse fails with `idempotency_conflict`.
- Closing and reopening Runtime retains the Profile and installed person Skill, and Plugin uninstall leaves SQLite data and a separately installed person Skill intact.
- The filesystem effect still precedes SQLite operation completion. Idempotent install/export/uninstall retries converge when the same resolved projection is retried, but this slice does not provide a durable pre-effect reservation. A concurrent mutation can claim the RequestId after the filesystem effect, and a retry of an unpinned install/export can resolve a different current Profile. Exhaustive crash injection, orphan collection, and durable cross-boundary coordination remain post-Preview hardening; this feature does not claim a cross-filesystem/SQLite atomic transaction.
## Verification
- `pnpm exec vitest run packages/engine/src/host/mutation-authority.sqlite.test.ts packages/runtime/src/preview.test.ts packages/bindings/src/full-binding.test.ts packages/cli/src/preview.test.ts packages/cli/src/lifecycle.test.ts`
- `node --test tests/check_package_boundaries.test.mjs`
- `pnpm run gates:fast`, `pnpm run typecheck`, `pnpm run build`, and `pnpm run hygiene`
- `python3 -B scripts/verify_docs.py` and `python3 -B scripts/verify_agent_notes.py`
- A fresh local Codex `0.146.0` process passed the opt-in discovery case with `DISTILLY_VERIFY_CODEX_DISCOVERY=/opt/homebrew/bin/codex`; the test runs `codex debug prompt-input` and observes the generated person Skill in the model-visible prompt input without making a model call.
@@ -1,34 +0,0 @@
# Agent Note: Developer Preview runtime
Status: implemented
## Problem
The SQLite business path and verified reads are runnable only through a package-private composition. The injected Distilly, MCP, and Panel surfaces therefore still depend on complete test clients and cannot bind a trusted actor, engine-owned lease owner, or verified briefing capacity to real local data. Waiting for every deferred maintenance, bundle, parser, and host method would keep the first local-material Preview unusable even though its vertical product path is implemented.
## Decision
Explicitly named Preview subpaths avoid claiming the production runtime contract. `@distilly/engine/preview` owns the real root-scoped SQLite core runtime, and `@distilly/runtime/preview` composes it as a local `EngineClient`. Each connection accepts only a trusted actor and optional capacity; the engine allocates a fresh opaque lease owner. Calls parse method parameters, mutation context, and results at the runtime boundary, then dispatch the methods backed by the live SQLite composition.
The twelve deferred MethodMap entries fail visibly with non-retryable `schema_unsupported` after their boundary input is validated. They never return placeholder success. The ordinary `@distilly/engine` and `@distilly/runtime` roots and the future production runtime names remain unavailable until the full contract closes. Runtime close stops new work, drains accepted calls, detaches client watches, closes SQLite, and releases in-process root ownership; client close affects only that session.
## Alternatives considered
- Exporting the target production `openEngine` and `LocalRuntime` now was rejected because it would falsely imply that host install, doctor, bundles, parsers, and every core method are implemented.
- Blocking runtime work until all 35 methods exist was rejected because the user explicitly prioritized a runnable local-material Developer Preview and deferred noncritical methods.
- Returning empty or synthetic results for deferred methods was rejected because it would make tests pass without a usable or auditable product.
- Adding a daemon, RPC protocol, cross-process takeover, adapters, parsers, or bindings in this feature was rejected because those are downstream slices and would prevent a small reviewable runtime commit.
- Letting callers supply `LeaseOwnerId` was rejected because it would break session isolation and trusted idempotency preimages.
## Consequences
The Distilly facade, five-tool MCP presenter, and later Panel/CLI composition can now borrow one real local client instead of a storage fake. Separate connections receive different engine-owned lease authority, watches detach per client, and runtime close rejects new work only after accepted calls drain. A second in-process owner for the same normalized root fails with `busy`; close or a failed open releases that reservation.
This Preview owns one root only within a process. A later launcher/service feature must provide authenticated cross-process attach or fail-closed ownership before Codex and Claude Code can safely use the same root concurrently. The explicit unsupported MethodMap entries are a temporary Preview compatibility surface; downstream main-flow code must not treat them as feature availability, and the production root exports remain gated on real handlers.
## Verification
- A real temporary root completes create, text ingest, pending, capacity-bound brief, owner-isolated commit, Profile, and prompt through the Preview client and remains readable after runtime close and reopen.
- Focused tests cover normalized same-root races, failed-open reservation release, strict open/session/params/context/result boundaries, per-client watch and close isolation, in-flight drain, and explicit core/runtime-owned unsupported methods.
- Built Engine and Runtime smokes import the package subpaths by name, keep both stable roots empty, and open/read/close a real SQLite root. Package-boundary fixtures admit only Runtime to Protocol plus Engine.
- Node 22 focused tests, formatting, ESLint, workspace typecheck and build, boundary and build-artifact tests, Engine pack, Knip, publint, Runtime type-package validation, documentation, Agent Note, and diff checks pass.
@@ -1,46 +0,0 @@
# Agent Note: Full Codex and Claude Code host bindings
Status: implemented
## Problem
The Developer Preview has verified host capabilities and a real five-tool runtime, but neither supported host has a production `HostBinding`. A setup command therefore cannot install a launchable plugin, validate its launcher, create a host-scoped injector, or remove only the host integration while retaining the Distilly data root.
## Decision
Concrete Codex and Claude Code full-binding factories now coexist with the unchanged capability-only factories. Each full binding reuses the existing fail-closed preflight, creates a host-tagged form renderer over a trusted outer presenter, provides prompt-only subrun injection and self-contained person-Skill projections, installs a rendered plugin tree with a real absolute-launcher `.mcp.json`, reports narrow on-disk doctor state, and removes only exact digest-verified owned files.
Codex uses the official personal marketplace source at `~/plugins/distilly`, preserves unrelated marketplace entries, validates the Distilly entry before any destructive command, and invokes the trusted absolute Codex executable to add or remove `distilly@<marketplace>`. The default runner binds `HOME`, `USERPROFILE`, and `CODEX_HOME` to the explicit home. Claude Code uses the official auto-discovered skills-directory plugin at `~/.claude/skills/distilly`. Both paths are rooted by an explicit factory option so fresh-HOME tests do not mutate the developer's real home.
Plugin replacement is staged outside host discovery under `~/.distilly/host-install`; an exact owned old tree remains as a backup until host registration succeeds. Failure restores the old tree and Codex marketplace bytes. Person-Skill staging uses the same non-discovered transaction root. Person replay and uninstall share an exact verified reader that rejects root or owned-file symlinks, validates the complete install reference and deterministic id, binds the manifest path to its root, and checks manifest plus actual content digests before returning or deleting anything.
## Alternatives considered
- A placeholder `kind: "full"` object with unsupported injector or lifecycle methods was rejected because the in-force host contract explicitly forbids placeholder full bindings.
- Writing Codex private cache/config records directly was rejected because the supported `codex plugin add/remove` commands own those records.
- Installing Claude Code directly into its marketplace cache was rejected because the cache is host-owned; the documented skills-directory plugin path is persistent, local, and auto-discovered.
- Copying `.mcp.json.template` into an installed plugin was rejected because installable output must contain a real launcher path and no sentinel.
- Deleting a whole shared marketplace or modified plugin tree during uninstall was rejected because ownership is narrower than either target.
- Writing staging or backup directories beside an auto-discovered plugin/Skill was rejected because a live host could transiently discover an incomplete duplicate.
- Replacing the active plugin before the new tree and host registration succeeded was rejected because a recoverable setup failure would disable an existing installation.
## Consequences
- Both factories are real and independently callable, while Runtime and CLI still do not compose them. The next lifecycle feature can depend on these factories without weakening the capability-only leaf.
- Installed plugin trees contain the platform manifest, canonical Skill bytes, a real `.mcp.json`, and no source template or launcher sentinel. Setup requires a restart because both hosts discover integrations at session startup.
- Plugin uninstall leaves `~/.distilly/` and separately installed person Skills intact. Person uninstall removes only its exact unmodified `SKILL.md` and ownership manifest; an unowned neighbor prevents directory removal but is never deleted.
- Codex setup intentionally uses the supported host command rather than writing host caches. Its process edge is isolated to the explicit home, but a complete packaged CLI still has to locate and validate the executable before constructing this binding.
- The narrow doctor checks owned bytes, launcher reachability, host, and release version. Deep diagnostics, cross-filesystem replacement, crash-orphan cleanup, and packaged fresh-session verification remain later Preview work.
## Verification
- `pnpm exec vitest run packages/bindings/src`
- `pnpm run gates:fast`
- `pnpm run build`
- `pnpm run typecheck`
- `pnpm --filter @distilly/bindings run snapshots`
- `pnpm run hygiene`
- `python3 -B scripts/verify_docs.py`
- `python3 -B scripts/verify_agent_notes.py`
- A temporary-home smoke used the installed Codex and Claude Code executables: Codex add/list/remove succeeded; Claude Code strict validation and list succeeded; both integrations were removed without touching the real home.
- Full `pnpm run snapshots` remains red only on the pre-existing stale `packages/panel/web/app.js` bundle and reproduces from clean base `d43f2ef`; the bindings snapshot is green.
@@ -1,35 +0,0 @@
# Agent Note: Local file ingest and Preview parsers
Status: implemented
## Problem
The real Preview runtime could complete a Profile only when a caller first converted every source into `MaterialInput`. That left the required user journey—select local material, retain its raw evidence, distill it, and reopen the result—behind a test or custom integration. The full parser roadmap contains several formats that are not necessary to prove this first vertical product path.
## Decision
The first local-material slice supports exactly UTF-8 TXT, Markdown, JSON, SRT, and VTT. `@distilly/adapters` owns deterministic parser contracts, exact media-type registration, stable JSON rendering, subtitle cleanup, fatal UTF-8 handling, and the no-truncation output bound. It performs no filesystem or authority writes.
`@distilly/runtime/preview` reads only explicit user-selected regular files, retains only each basename as the public label, selects the built-in parser, and returns raw bytes plus any validated draft through a trusted seam. Engine derives `RawId` from the complete raw bytes, binds successful drafts to `raw_extract`, and commits the subject/raw relation, accepted material, generation, pending job, operation result, and events in one SQLite transaction. Each raw authority row can claim only one canonical text tuple; a later parser interpretation with different content, kind, method, or producer fails inside that transaction instead of entering briefing beside the first. Parser failure keeps a raw-only item without changing the material set or enqueueing work. RequestId replay and conflict resolution happen before Runtime reads a path.
Material reads verify both canonical text and any referenced raw blob before reporting `rawAvailable=true`. Raw blob publication may precede the SQLite transaction and leave an unreferenced content-addressed blob after failure, matching the existing ingest transaction model; it cannot expose partial product state.
## Alternatives considered
- Adding PDF, EML/MBOX, Lark export, or OCR now was rejected because the user explicitly limited the first Preview to the five formats needed for the local vertical path. Those formats remain later independent parser features.
- Letting parsers persist raw bytes or assign `RawId` was rejected because Engine must remain the only authority writer and identity owner.
- Sending file paths through MCP was rejected because the five-tool model boundary is unchanged; explicit file selection belongs to the direct user/runtime surface.
- Storing absolute paths as provenance was rejected because local filesystem topology is private and unnecessary for replay, audit, or evidence reads.
- Treating parser failure as an ingest failure was rejected because retaining the immutable raw evidence allows a later supported extraction while correctly preventing invisible content from entering distillation.
## Consequences
The Preview can now create a subject from user-selected local files, preserve unsupported or malformed inputs as raw-only evidence, and distill deterministic text without another model API key. Replaying a completed file ingest remains stable even after the selected file is moved or deleted, while a changed input or actor conflicts before any read. The stable Runtime root remains empty, source providers remain absent, and the parser set makes no claim about PDF, mail archives, Lark exports, images, or OCR.
The schema adds raw material and subject-to-raw authority tables. Existing unreleased Preview roots created under the earlier private schema are not migrated by this feature; compatibility migration remains deferred until after the runnable Developer Preview path.
## Verification
- Adapter tests cover exact registry behavior, UTF-8 failure, whitespace refusal, stable JSON, SRT/VTT cleanup, and exact/+1 output byte bounds.
- A real Runtime root atomically ingests parsed and unsupported files, preserves raw-only zero-delta state, replays before deleted-file reads, rejects changed input/actor/method reuse, refuses a second canonical interpretation of identical raw bytes, verifies raw CAS bytes and `rawAvailable` after reopen, and leaves no product-visible state or absolute path on read failure.
- Node 22 focused tests, formatting/lint, typecheck, build, package boundaries, built package smokes, pack/export checks, documentation, Agent Note, and diff checks pass.
@@ -1,37 +0,0 @@
# Agent Note: Developer Preview CLI lifecycle
Status: implemented
## Problem
The Preview had a real local Engine/MCP graph and concrete Codex/Claude Code bindings, but no executable connected them. A developer could not run one setup command, obtain an absolute launcher, diagnose the host projection, start the five-tool server through that launcher, or remove the host integration without hand-written composition.
## Decision
The repo-local Developer Preview binary exposes only `setup`, narrow `doctor`, `uninstall`, and the plugin-owned `mcp --host` entry. Setup validates Node and the supported macOS/Linux platform, resolves the selected executable, probes its exact `--version`, and requires a matching immutable host/version/environment/release/wire/Skill net-capacity fixture before writing. It then writes digest-owned launcher and runtime locator bytes under `~/.distilly`, invokes the full host binding, and records the observed host identity in a private lifecycle manifest.
The launcher records exact absolute Node and built-entry paths. Every lifecycle parent is checked as a real non-symlink directory before write or delete. The first enabled projection records the fixed Codex host argument; the public CLI rejects Claude Code until that exact host has equivalent real capacity evidence. The lifecycle manifest owns no person data. Doctor and uninstall use the same exact plugin file-set verification. Uninstall verifies all bootstrap bytes before mutation, removes the Codex projection and launcher/runtime locator, and never recursively removes `~/.distilly`, SQLite, blobs, Profiles, or person Skills.
The Codex net-capacity record is not a guessed constant. A bounded governance harness launches the current five-tool server through real `codex exec`, verifies a 65,536-byte result retains deep-equal `structuredContent` and repeated JSON text, verifies a separate 16,384-byte `HostDistillBriefing`, and requires the model to return unseen tail markers from both independent sessions. The immutable schema-versioned record binds that observation to the exact host version, environment, release, wire version, canonical Skill digest, tool-contract digest, and serializer. Unknown tuples and Claude Code without its own record fail closed before product writes.
## Alternatives considered
- A launcher containing bare `distilly mcp` was rejected because setup cannot depend on global `PATH` after installation.
- Inheriting host identity from ambient shell state was rejected because each owned plugin can pass its exact host argument.
- Treating any executable with the right filename as a supported host was rejected because host identity and briefing capacity require an observed exact version and matching fixture.
- Registering every final CLI command as an unsupported placeholder was rejected because the Preview exposes only commands that actually run.
- Copying the workspace as a pretend runtime was rejected because the later packaged Preview owns self-contained release assembly; this slice launches one checked built entry.
- Removing the whole `~/.distilly` root during uninstall was rejected because product data belongs to the user independently of plugin/bootstrap state.
- Claiming Windows support with a POSIX launcher was rejected; the first repo-local lifecycle fails visibly outside macOS/Linux.
## Consequences
Developers with the verified Codex version can build the workspace and run the same executable for setup, doctor, MCP startup, and uninstall. Unknown or updated versions and Claude Code fail before product writes until a matching real-host capacity fixture is reviewed. Same-release setup is replay-safe and lifecycle or plugin-tree tampering fails closed. The binary is intentionally not the final public CLI: stable library exports remain narrow, data subcommands are absent, and Claude activation, versioned code copying, upgrade/rollback, deep Engine doctor, cross-process attach, and a self-contained `0.1.0-preview.1` archive remain later slices.
## Verification
- `node packages/cli/scripts/verify-host-capacity-fixture.mjs codex` — the real Codex 0.146.0 host surface passes both fixed boundaries and produces the committed immutable evidence record.
- `pnpm exec vitest run packages/cli/src packages/bindings/src/full-binding.test.ts` — focused lifecycle, evidence, composition, and binding tests pass, including unknown-version, unverified-Claude, parent-symlink, and exact doctor ownership regressions.
- `pnpm run test:boundaries` — 49 tests pass with the explicit CLI-to-bindings edge.
- `pnpm run build` — the binary, lifecycle, bindings, and existing Preview composition compile together.
- `pnpm --filter @distilly/cli run smoke:built` — the real built binary installs Codex in a temporary home, rejects deferred Claude activation, doctor reports a healthy projection, the installed absolute launcher exposes exactly five MCP tools through the official client, uninstall succeeds, and SQLite plus person data remain.
@@ -1,32 +0,0 @@
# Agent Note: Developer Preview MCP composition
Status: implemented
## Problem
The real SQLite Preview runtime, browser-safe facade, five-tool MCP adapter, and loopback Panel existed only as separate injected seams. The only stdio child still used a complete fake EngineClient, so no owned graph proved that one local authority could serve `Distilly`, `Person`, MCP, correction review, and a later process reopen.
## Decision
The private `@distilly/cli` package now exposes only the explicit `@distilly/cli/preview` composition. A trusted caller supplies the local root, host session and verified capacity, plus fixed Panel assets and port. The composition opens one Preview LocalRuntime, binds a capacity-bearing host client for MCP and a fixed-capacity direct-user client for the facade and Panel, registers exactly the existing five tools, and lazily starts the real authenticated Panel only when a suspended review is presented.
The composition owns teardown but performs no host installation. Stdio serving is single-flight and tracked. Teardown first stops and drains MCP and waits for its stdio transport, then closes the Panel presenter, both borrowed clients, and finally the runtime; an admitted correction can therefore finish presenting review before Panel shutdown. The stable CLI root remains empty and there is no executable, setup, doctor, uninstall, host manifest mutation, or cross-process attach in this feature.
## Alternatives considered
- Making MCP import Runtime was rejected because the presenter must remain transport-neutral and testable over an injected client.
- Making Runtime import MCP or Panel was rejected because transport and UI are outer composition concerns.
- Adding a test-only real-client fixture without a product composition was rejected because it would improve evidence without creating a reusable path for the host launcher.
- Shipping a partial command shell with unsupported setup and lifecycle commands was rejected because it would look runnable while returning placeholder failures.
- Returning a synthetic review URL was rejected because correction must prove the existing real loopback Panel boundary.
## Consequences
Bindings and the future CLI executable can now invoke one reviewed outer graph instead of rebuilding actor, capacity, presenter, and close ordering. Direct facade operations and MCP operations share the same SQLite root while retaining separate lease owners and actor identities. The current composition is still in-process and explicitly Preview-only; it cannot safely serve two independent host processes until the launcher/service feature supplies authenticated attach or fail-closed ownership.
## Verification
- A real temporary root completes create, ingest, brief, claim commit, `Person.get`, `Person.prompt`, direct correction, and close/reopen through the composed facade.
- A built child initializes over stdio, lists exactly five tools, calls ingest, pending/brief, commit, get, and host-relayed correction against SQLite, verifies the real Panel health endpoint, closes, reopens a second process, and reads the persisted Profile with no fake EngineClient.
- Focused lifecycle coverage closes an actively served stdio graph, waits for transport teardown, and immediately reopens the released root.
- Node 22 focused Vitest, built smoke, build, typecheck, fast gates, package-boundary tests, and build-artifact tests pass.
@@ -1,38 +0,0 @@
# Agent Note: Source adapter contracts
Status: implemented
## Problem
The Developer Preview source-adapter design defines direct and delegated collection seams, opaque secret references, and a separate direct-user collection method table, but the workspace had no package that owned those contracts. Adding provider implementations before this boundary existed would have encouraged each adapter, Panel, or CLI path to invent a different resource shape, config format, or validation rule.
## Decision
`@distilly/adapters` is a contract-only package with a Protocol-only internal dependency. It exports the direct/delegated `SourceAdapter` union, adapter-owned strict resource parsers, public config plus opaque secret references, a content-free registry view, and strict runtime schemas for exactly `source.list`, `source.configure`, `source.preflight`, and `source.collect`.
The generic user-action resource accepts bounded finite JSON so community adapters can define their own shapes, while every registered adapter must supply the strict parser used before provider-specific dispatch. Config validation keeps secret-like keys out of public values and never resolves an opaque secret reference. The registry validates registrations and exposes frozen metadata snapshots, not callable adapter handles.
The package adds no built-in adapter ids, HTTP clients, credential resolution, collection service, MaterialParser surface or implementation, runtime composition, CLI command, EngineMethodMap method, or MCP tool. Those implementations remain owned by the proposed [Developer Preview source-adapter boundary](../../proposed/architecture/2026-08-30-developer-preview-source-adapters.md).
## Alternatives considered
- Putting adapter contracts in Protocol was rejected because source collection is not an EngineMethodMap or model wire capability; only shared product values belong in Protocol.
- Starting with Lark or another concrete adapter was rejected because provider code would prematurely own the generic configuration, validation, and user-action boundary.
- Exporting registry lookup handles was rejected because it would give unvalidated callers a way around each adapter's strict resource parser; the public registry view remains content-free.
- Adding secret resolution to the config contract was rejected because resolved values must exist only inside later production composition during preflight or collect.
## Consequences
Concrete adapters, parsers, and the later user collection service now have one typed boundary to implement without changing Protocol or the five-tool MCP surface. The open generic resource envelope remains safe only as a bounded transport value; provider-specific semantics and unknown-key rejection still belong to the adapter-owned parser. This feature proves registration and boundary validation, not that any provider can collect data.
## Verification
- `pnpm run gates:fast`
- `pnpm exec vitest run packages/adapters/src`
- `pnpm run test:boundaries`
- `pnpm run typecheck`
- `pnpm run build`
- `pnpm run snapshots`
- `pnpm run hygiene`
- `python3 -B scripts/verify_docs.py`
- `python3 -B scripts/verify_agent_notes.py`
@@ -1,36 +0,0 @@
# Agent Note: SQLite commit migration
Status: implemented
## Problem
The live SQLite path can create subjects, ingest material, and issue briefing leases, but `distill.commit` still exists only as a test-only file-authority fixture. A successful host patch therefore cannot atomically become immutable claim/version authority, and the SQLite briefing path cannot yet produce an incremental baseline after a first commit.
## Decision
The private schema-v1 authority includes immutable versions, canonical material membership, version-scoped claims and evidence, independent version status, same-subject lineage constraints, and unique current/suspended status. `distill.commit` uses one SQLite transaction that rechecks the active job, lease owner, generation, current/suspended pointers, material membership, and pinned contract before inserting all version facts, updating exactly one current or suspended pointer, deleting pending work, recording the stable operation result, and appending fixed events.
The existing deterministic evidence, claim, quality, review-gate, version-id, and renderer functions remain pure preparation logic. A targeted verified version reader checks the directly returned authority rows, canonical memberships, deterministic identities, renderer/source-grouping dispatch, status, lineage subjects, and a sealed envelope that binds the accepted-patch digest. Ingest and briefing use that reader for the current baseline. Projections, review actions, correction, and a runtime remain outside this feature.
## Alternatives considered
- Keep the file commit service beside SQLite and copy its result into the database. This loses the one-authority and one-transaction guarantees and would preserve the recovery protocol this feature exists to retire.
- Store only a rendered profile or one opaque version JSON document. This prevents relational evidence membership checks and makes later review, doctor, and verified reads depend on an untyped duplicate authority.
- Migrate commit, promote/reject, rollback, and correction together. Those mutations share concepts but have different state transitions; combining them would make the critical storage boundary too large to review and would violate the required serial landing order.
## Consequences
SQLite is now the sole live commit authority and can feed later incremental briefing without a file-version fallback. The partial unique status indexes require an incremental current commit to retire the previous current inside the same transaction before inserting the new current status; rollback restores both if any later write fails. The former file commit, staging paths, state swap, and semantic recovery survive only as explicitly named test fixtures for the still-unmigrated read/review tests.
## Risks
The private schema is still unreleased, so changing schema v1 is intentional, but every exact-schema test and build-pack guard must move in the same feature. Ordinary reads must validate the rows and blobs they directly use without turning this slice into the later full-history doctor or projection feature.
## Verification
- Exact retry replays the stored `CommitResult`; reuse of the RequestId with different params, actor, or lease owner fails with `idempotency_conflict`.
- Invalid or stale evidence, base, generation, contract, lease, or active suspended state performs zero business writes and preserves pending work.
- A valid patch writes immutable version metadata, canonical material/claim/evidence membership, one independent status, the current or suspended pointer, operation result, and two events in one SQLite transaction, then removes the pending job and lease.
- Process termination before SQLite commit exposes only the previous state; termination after commit exposes the complete target without mutation-specific recovery.
- A committed current version is a verified baseline for later ingest and briefing, including canonical incremental refs and stable brief replay.
- Legacy staging/recovery regressions remain green from their test-only locations. The built Engine smoke executes ingest → brief → commit, closes, reopens, and replays from packaged output; build-pack tests accept the new production commit module and reject the retired staging module and all fixtures.
@@ -1,34 +0,0 @@
# Agent Note: SQLite correction migration
Status: implemented
## Problem
SQLite owned the live subject, material, pending, version, review, operation, and event authority, but `profiles.correct` had no package-internal service over that authority. A correction therefore could not preserve its private text as evidence, replace claims deterministically, or produce a reviewable immutable version without inventing a second ingest/commit sequence or reviving the paused file-journal implementation.
## Decision
The package-internal composition owns one SQLite-backed `CorrectionService`. Its boundary normalizes a `CorrectionDraft`, binds direct-user or relayed provenance to the trusted actor, and stores the canonical correction body through the shared content-addressed blob store. The current version or explicitly targeted active candidate supplies the content baseline, while the transaction-time current remains the quality baseline. One full-body replacement claim is always `user_asserted`; explicit targets become superseded by that same claim.
One SQLite transaction rechecks RequestId replay, subject generation, current/candidate pointers, material membership, and claim targets before writing the correction material reference, generation-plus-one membership, immutable current or suspended version, optional candidate replacement lineage, pointers/statuses, a fresh unleased pending job, stable result, and fixed events. A successful current correction intentionally permits `addedMaterialCount = 0`, while ordinary duplicate ingest still does not create a meaningless zero-delta job. Correction uses no journal, staging directory, recovery protocol, or dual write.
## Alternatives considered
- Implementing correction as `materials.ingest` followed by `distill.commit` was rejected because it exposes an intermediate pending state, requires a model patch for a direct assertion, and loses one-transaction idempotency.
- Reusing the paused file-journal correction work was rejected because it would reintroduce a second authority, target-first recovery, and correction-specific cleanup.
- Treating correction text as metadata was rejected because the replacement evidence could not then verify, retain, or later purge its actual private body.
- Migrating general verified reads and projections in the same feature was rejected because they are consumers of correction authority, not part of its critical mutation.
## Consequences
Direct user correction can become current when no mechanical review reason exists; every non-user correction records matching relayed provenance and suspends with `relayed_correction`. An explicitly targeted active candidate can be replaced atomically while preserving its rejected lineage. The shared pending schema now accepts zero added materials, and its verified readers support the resulting empty incremental briefing without changing ordinary ingest enqueue behavior.
The private unreleased schema-v1 contract and Protocol correction byte limit move together: correction text is capped at the claim-text limit of 16 KiB. The Engine package remains package-internal; this feature does not create a runtime, CLI, binding, Panel composition, or installable Plugin.
## Verification
- Focused correction tests cover normalization, direct and relayed outcomes, candidate replacement, exact RequestId replay/conflict, concurrent serialization, atomic rollback, zero-delta pending/brief behavior, and ordinary duplicate-ingest regression.
- Real child processes receive `SIGKILL` immediately before and after correction COMMIT. Reopen sees only the previous world or the complete target; post-COMMIT retry replays without allocating ids or adding rows/events.
- The built Engine smoke executes direct and relayed correction, closes, reopens, and replays both from packaged output.
- Production composition and package gates reject legacy authority plus correction journal, transaction, staging, and recovery artifacts; the dry-run Engine package requires the real correction service.
- Node 22 typecheck, build, focused boundary tests, build-artifact tests, Engine smoke, and the 314-file Engine pack check pass.
@@ -1,34 +0,0 @@
# Agent Note: SQLite Preview verified reads
Status: implemented
## Problem
The live mutation path committed subjects, materials, versions, review decisions, rollbacks, and corrections to SQLite/WAL, but most read services still depended on the retired file authority and JSON Library projection. A runtime built on those services would either miss committed SQLite state after reopen or revive a second authority before the Developer Preview could run end to end.
## Decision
One package-internal SQLite read service implements only the reads consumed by the first Preview: subject list and resolution; Profile get, prompt, and status; material list and get; version list, diff, and lineage; and Library list. Each operation uses a SQLite read snapshot and verifies the authority rows, immutable versions, events, and content blobs required by its result. Material reads acquire shared blob access before the SQLite snapshot and retain it through digest-verified delivery.
Current material rows are rebound to the authoritative subject material-set hash, and any pending total must match the same verified membership. The Library derives privacy and status from that membership in the same SQLite snapshot instead of introducing a durable projection. Existing deterministic cursors, profile and prompt rendering, semantic diffing, source grouping, and immutable-version verification remain the shared algorithms. The root composition exposes these reads without exporting a runtime.
## Alternatives considered
- Keeping the file-backed read services was rejected because they cannot observe the live SQLite authority and would preserve journal, recovery, and lock dependencies that the migration is removing.
- Rebuilding the JSON Library projection in this feature was rejected because SQLite can answer the Preview query directly and the projection would add reconciliation and failure modes without serving the vertical path.
- Adding a generic read repository or projection framework was rejected because the first runtime has a known, small consumer surface and no demonstrated second implementation.
- Combining runtime, parsers, or host bindings with the storage reads was rejected because those are downstream features that need this query contract committed first.
- Physically relocating the retained file-read regression surface was deferred because it would turn the vertical slice into a broad test-fixture migration; the live composition has no dependency on it.
## Consequences
Create, ingest, commit, correction, review, rollback, and reopen now feed the same verified subject, Profile, material, version, lineage, review, and Library reads. A missing pending-only material fails closed instead of silently lowering Library privacy. The Library is currently an in-snapshot local aggregate, so it does not need a projection watermark or repair protocol.
The first Preview intentionally aggregates all local subjects for subject and Library filtering. Lineage currently validates all events for the selected subject before applying its page boundary. Event correlation covers canonical EventRecord bytes, checksum, request, actor, and subject scope, but deeper operation-method/result correlation is deferred. Subject resolution requires the stored canonical URL or provider-qualified locator after basic query normalization; equivalence normalization for alternate URL spellings is also deferred. These bounded hardening items do not introduce a second authority and remain outside the local-material Preview critical path.
## Verification
- Focused SQLite tests cover create, ingest, commit, current and historical Profiles/materials/versions, prompt, semantic diff, lineage, method/filter-bound cursors, subject resolution, Library search/status/privacy, close/reopen, suspended review, reject, correction, rollback, and material-set corruption.
- The material corruption case deletes one pending-only row and proves both material and Library reads fail with `storage_corrupt` rather than returning an incomplete or less-private snapshot.
- Related SQLite composition tests and retained file-read/projection regressions pass; the live composition source graph contains no `CommittedVersionReader`, JSON Library projection, file journal, staging, or recovery dependency.
- Node 22 formatting, ESLint, full workspace typecheck, build, `gates:fast`, package-boundary checks, Engine pack, documentation verification, Agent Note verification, and `git diff --check` pass.
@@ -1,37 +0,0 @@
# Agent Note: SQLite review and rollback migration
Status: implemented
## Problem
SQLite is authoritative for immutable committed versions, claims, evidence, status pointers, pending jobs, operations, and events, but active-review reads and promote/reject/rollback mutations previously existed only in the file-authority test fixture. The live storage path therefore could not finish a suspended candidate decision or create an immutable rollback descendant without the review journals, staging, and semantic recovery that the SQLite design retires.
## Decision
The package-internal SQLite composition owns active-review listing and one `ReviewService` for promote, reject, and rollback. Review listing uses one read snapshot and validates only the directly needed subject pointers, active statuses, immutable current/candidate rows, memberships, and claims before rendering the canonical diff; it does not read pending work or scan full history.
Each mutation resolves global RequestId replay/conflict, rechecks its subject and active pointers inside one short SQLite write transaction, records the stable result and fixed event tuple, and publishes only after commit. Promote makes the prior current historical and the exact active candidate current. Reject makes the exact candidate rejected while preserving the existing pending row and lease byte-for-byte. Rollback accepts a same-subject historical target and inserts a new immutable current descendant whose semantic snapshot comes from that target while parent, creation source, actor, time, and `VersionId` belong to the new mutation. Promote and rollback recompute the material delta against the new current; remaining work receives a fresh JobId, mutation-time `queuedAt`, and no lease, including when no old pending row exists.
The pure `summarizeVersion` projection lives in an internal dependency-light module so production review composition does not inherit the still-unmigrated file reader's type graph. The former file review/rollback service and its recovery branches are deleted. A copied file review query remains only as an explicitly named test fixture for the unmigrated read suite, while a source-graph gate prevents any legacy authority, lock, recovery, queue, or projection from becoming transitively reachable from the SQLite composition.
## Alternatives considered
- Keep using the file review fixture against SQLite-created versions. This would make two authorities cooperate across incompatible transaction boundaries and preserve the recovery protocol being retired.
- Update the current pointer to an old row during rollback. This would erase the rollback action from immutable lineage and give historical bytes a new actor/time without a new `VersionId`.
- Import the pure version-summary helper from the legacy-coupled version service. The source dependency would pull file facts and subject locks into the production composition even though TypeScript erases some imports, so the helper is isolated instead.
- Land correction with review/rollback. Correction depends on these transitions but also adds a correction blob and replacement-claim semantics, so combining them would make the critical SQLite feature too large to review.
## Consequences
SQLite/WAL now decides whether a review or rollback mutation committed; there is no review/rollback TransactionRecord, staging directory, target/previous/third-state recovery, or dual write in the live path. Exact replay does not allocate new ids or duplicate rows/events, and a changed method, normalized input, or trusted actor for the same RequestId fails with `idempotency_conflict`.
The Engine package root remains empty and no public runtime or user installation is introduced. File-backed profile/material/version/Library reads, their coordination primitives, and retained lease/commit recovery remain packaged but uncomposed until their owner migrations. Legacy persistence types still present in Protocol are not removed by this feature; the later authority/Protocol cleanup owns that closure. Correction, projections, doctor, and production event/outbox composition also remain later features.
## Verification
- Real temporary SQLite roots cover first-version and incremental candidates, filtered/paged review reads, canonical diffs, pointer/status corruption, promote/reject/rollback transitions, event reason/source metadata, exact replay/conflict, competing decisions, and all pending/lease rules.
- Real child processes receive `SIGKILL` immediately before and after COMMIT for promote, reject, and rollback; reopening observes either the complete previous world or the complete target and then replays exactly without mutation-specific recovery.
- Rollback tests compare target and descendant material, claim, evidence, quality, renderer, and accepted-patch authority while requiring a new immutable id, current parent, actor, time, and rollback creation edge.
- The complete Engine source suite catches transitive legacy imports; the dependency-light summary extraction restores a clean SQLite composition boundary. Package-boundary fixtures also reject production imports of named legacy test fixtures.
- Build-artifact and dry-run pack checks require the real SQLite review/query composition, reject retired review runtime markers and modules, exclude all test fixtures, and run suspended commit through promote, immutable rollback, reopen, and exact replay from built output.
- Node 22 focused review, read-regression, boundary, typecheck, build, pack, and hygiene checks pass; documentation, Agent Note, and whitespace verification pass for the completed feature tree.
@@ -1,35 +0,0 @@
# Agent Note: Chat-first source intake
Status: implemented
## Problem
The five-tool Preview could ingest text from host research and readable local sources, but its canonical Skill described capabilities and provenance without making user-supplied handles the default interaction. A model could respond to attached files, a selected directory, or public URLs by asking the user to classify the person, choose a source lane, or repeat information already present. Larger selections also lacked an explicit rule to finish collection before briefing, creating a risk of committing an intermediate generation.
## Decision
Make supplied sources the chat-first intake plan after subject resolution. Pasted text, explicitly selected readable files/directories, and public URLs need no connector setup or intake form. The host reads only that scope with capabilities visible in the current session, preserves one MaterialInput per traceable source, submits repeated selected handles only once per intake, uses `enqueue: auto` for intermediate batches and `enqueue: now` for the final batch at the existing 32-material tool limit, and briefs only the final pending generation.
Because real-host directory testing exposed hand-counted 30- and 31-character RequestId suffixes, the same orchestration now requires a safe host-local UUID or 16-byte random-hex value when that facility is available, followed by explicit lowercase, de-hyphenation, and 32-character validation. The generator is used only for an identifier and cannot read source data or mutate Distilly.
Real-host public-URL testing also exposed a partial publication month sent through the exact-time `publishedAt` field. A month or year visible on a page remains useful source text, but the structured field is now omitted unless the source supplies a complete timestamp; the workflow never invents a day to make partial metadata validate.
Directly supplied private material plus an explicit distillation request authorizes only that content. It does not authorize adjacent paths, conversations, accounts, contacts, or public identity expansion. Directory traversal does not follow symlinks and skips credentials, tool state, dependency/VCS trees, binaries, and unrelated files. The model-facing path continues to ingest text; it does not claim the Runtime's separate raw-file path stored an attachment.
## Alternatives considered
- Add a sixth MCP source or file tool. Rejected because the five-tool contract is locked and host source acquisition already precedes text ingest.
- Have the Skill invoke `distilly ingest` or future `source collect` through shell. Rejected because model shell access must not bypass the user-bound CLI/adapter authorization surface or write Distilly state outside the five tools.
- Start with Lark, Slack, or another connector. Rejected because user-selected files, pasted text, and public URLs cover the zero-configuration path and do not require a secret or provider account.
- Scan nearby files or signed-in browser state automatically. Rejected because low interaction cannot mean hidden scope expansion.
## Consequences
A user can start with a person plus files, a directory, URLs, or pasted text and reach distillation without preliminary classification. Ambiguous identity, unclear directory scope, unreadable material, missing private authority, and oversized individual sources still pause visibly. Deterministic raw-file parsing and provider adapters remain separate user-side surfaces; this slice changes the installed orchestration, not Protocol, Engine storage, or the exact MCP tool set. Local text without a stable URI or artifact locator can become a distinct MaterialId in a later RequestId even when its body is unchanged; source grouping conservatively collapses exact republication for quality, so this feature does not claim cross-run physical deduplication.
## Verification
- `quick_validate.py plugins/shared/skills/distilly` validates the canonical Skill, and plugin assembly tests/check mode prove the Codex and Claude Code mirrors are byte-identical to it.
- The real Codex 0.146.0 capacity probe binds the final canonical digest to exact 65,536-byte tool-result and 16,384-byte briefing observations with both unseen tail markers preserved.
- Focused capacity-fixture, docs, Agent Note, build, and self-contained Codex package checks cover the release evidence and fresh installation surface.
- An isolated real Codex home completed four fresh model runs without an intake form: one Markdown file produced a current seven-claim profile; a directory preserved three Markdown/JSON/VTT sources while leaving `.env`, dependency content, and an outside-target symlink unread; one supplied public page produced a current profile with the exact URL and no linked-page crawl; and one pasted observation plus one local file remained two materials and produced a current profile.
@@ -1,45 +0,0 @@
# Agent Note: Codex self-contained Preview package
Status: implemented
## Problem
The Codex vertical Preview runs from compiled workspace files, but setup records that workspace entry in its launcher. Moving or deleting the checkout therefore breaks MCP, doctor, and uninstall. The source-only plugin tree also contains a launcher sentinel and is not an installable archive. There is no packaged acceptance test proving that the product survives removal of its unpacked bootstrap directory or that a user can persist a completed Profile as a Codex person Skill.
## Decision
Assemble one Codex-only `0.1.0-preview.1` directory from production build output. Bundle the CLI/runtime dependency graph into a small Node ESM tree, copy only the runtime prompts, Panel assets, release manifest, Codex manifest, and canonical Skill, and record every packaged file digest in a private runtime manifest. Omit the MCP template, Claude tree, source, tests, fixtures, and development dependencies. `--force` may replace only an exact previously verified runtime package, and does so only after the replacement staging tree has assembled successfully; it never recursively removes an arbitrary existing directory.
Packaged setup verifies the source manifest, atomically copies the exact bundle into `~/.distilly/runtime/0.1.0-preview.1/`, writes an absolute launcher to the copied entry, and installs a Codex plugin tree containing a generated real `.mcp.json`. The installed CLI reuses that copied tree for doctor, MCP, person installation, and uninstall. A narrow human command installs a current Profile by subject id through the existing `Person.install` path; the five model-facing MCP tools remain unchanged. Last-host uninstall removes only the verified owned runtime and plugin projection, preserving SQLite, blobs, and separately installed person Skills.
## Alternatives considered
- Shipping the pnpm workspace or a `pnpm deploy --legacy` tree was rejected because it is much larger and contains symlinks whose targets can escape or depend on the checkout.
- Keeping the launcher pointed at the unpacked package was rejected because deleting the bootstrap directory would break a supposedly installed product.
- Adding a sixth MCP tool for installation was rejected because the exact five-tool contract is a product invariant; the human CLI is the narrow explicit confirmation surface.
- Enabling Claude Code in the same package was rejected because the user requested Codex first and Claude does not yet have equivalent verified host evidence.
- Embedding the source `.mcp.json.template` was rejected because it is an assembler fixture, not an installable component; the full binding already owns deterministic `.mcp.json` generation.
## Consequences
- The package is versioned `0.1.0-preview.1`, contains only production Codex bytes, and has no sentinel, MCP template, test fake, symlink, or checkout path.
- Setup from a path containing spaces and non-ASCII characters installs a verified runtime copy and absolute launcher; deleting the unpacked package does not break doctor or MCP initialize/tools-list.
- The installed Codex plugin validates, has a real `.mcp.json`, and a fresh real Codex 0.146.0 process discovers the Distilly Skill.
- The packaged flow creates a person, ingests local material, commits and retrieves a Profile, applies and reviews a correction, installs the person Skill, and a fresh Codex process discovers that Skill.
- Uninstall removes the owned plugin, launcher, and versioned runtime while preserving SQLite/person data and the independently installed person Skill.
- Tampering with an owned runtime byte makes doctor fail and prevents destructive runtime removal.
- A populated unowned output directory is rejected without changing its contents; package rebuild can replace only a fully verified prior Distilly artifact.
The first package is intentionally Codex-only and macOS/Linux-only. It does not implement upgrade, daemon attach, deep Doctor, backup/restore, or Claude activation. Runtime installation is a file projection around the existing in-process single writer, so concurrent host processes remain unsupported. The source-to-installed copy is not a business transaction; ownership digests and fail-closed deletion limit its recovery surface until a later upgrade design.
The bundle must be built only from compiled package output. The repository TypeScript path aliases are therefore disabled for esbuild, and the assembler rejects any `packages/*/src/` input. Without both checks, a source and compiled copy of Protocol can enter the same bundle; typed errors then fail `instanceof` checks across those copies and become generic internal errors in the Panel.
## Verification
- `pnpm exec vitest run packages/cli/src packages/bindings/src/full-binding.test.ts packages/mcp/src/server.test.ts packages/panel/src/server-http.test.ts` under Node 22: 61 tests passed.
- `pnpm run gates:fast`, `pnpm run typecheck`, `pnpm run build`, `pnpm run hygiene`, the built CLI and package-safety smokes, plugin assembly/check, documentation validation, and Agent Note validation passed.
- `pnpm run package:preview:codex` produced the local 2.2 MiB `0.1.0-preview.1` artifact with no symlink, source input, workspace dependency, test path, sentinel, or checkout path.
- The Codex plugin validator accepted a plugin installed from the assembled bytes.
- The real-package verifier passed with Codex CLI 0.146.0 from a path containing spaces and non-ASCII characters. It removed the unpacked source, then proved official Plugin/MCP discovery, server release identity, exactly five descriptor-equal tools, create/ingest/brief/commit/get/prompt, relayed correction and Chromium Panel promotion, explicit person-Skill installation, fresh-process Skill discovery, reopen, and uninstall with byte-identical SQLite and person-Skill preservation.
- The immutable release-capacity fixture was regenerated through real Codex observations at the 65,536-byte tool-result and 16,384-byte briefing boundaries.
- The full pre-existing workspace test command still contains an unrelated stale Protocol assertion that submits 65,536 UTF-8 correction bytes even though the committed wire limit is 16,384; the same assertion is present at the feature base and is deferred rather than folded into this package commit. A read-service timeout seen only while the full suite, hygiene, Chromium, and real Codex verifier ran concurrently passed immediately when rerun alone.
@@ -1,32 +0,0 @@
# Agent Note: Developer Preview installation and community documentation
Status: implemented
## Problem
The root README, installation guide, contribution guide, roadmap, and language mirrors still described the legacy Python `dot-skill` product. A reader following those pages would miss the TypeScript Developer Preview, use obsolete commands, or assume that every listed host was already verified. The branch also had no visible update explaining the request for additional coding-agent Plugin contributors.
## Decision
Make the current `codex/distilly-plugin` Preview the only installation path documented on this branch. The root README and `INSTALL.md` now give separate agent and human instructions using the built `distilly` CLI, state the Node/pnpm and exact Codex evidence requirements, describe the five MCP tools and local parser scope, and explain that Plugin uninstall preserves person data. The roadmap and new `UPDATES.md` identify Codex as the first verified host, keep Claude Code and other hosts explicitly unverified, and ask the community to build Plugin packages for Grok Bot, Claude Code, OpenCode, Pi agent, and DeepSeek Harness (DSH), with active maintainer review.
All README and roadmap language mirrors now point to the same Preview branch and no longer expose legacy Python or `dot-skill` installation commands. The historical name is retained only as a short explanatory label in the root README. Current architecture, testing, and release documents remain the source of detailed implementation claims.
## Alternatives considered
- Keeping the old README and adding a banner was rejected because stale commands below the banner would still be easy to copy.
- Claiming `npx` or a registry install was rejected because this Preview has no published npm package.
- Listing every existing capability factory as a verified host was rejected because exact host/version/capacity evidence exists only for Codex.
- Updating only the root English page was rejected because the language mirrors would continue to advertise obsolete installation paths.
- Mixing the community request into a one-line roadmap item was rejected because host contributors need the exact target list and acceptance expectations in a visible update.
## Consequences
A new reader can clone the intended branch, build the current product, run setup/doctor, restart the host, and remove the integration without following legacy instructions. The documentation makes the Preview boundary explicit: Codex is verified, other requested hosts are community work, local parsers are intentionally limited, and no registry release is implied. The language pages are concise current summaries rather than full duplicated manuals; the root README remains authoritative.
## Verification
- `python3 -B scripts/verify_docs.py` passes all Markdown link, anchor, and file-ending checks.
- Prettier check passes for the root docs and all README/roadmap mirrors.
- Manual link review confirms the branch checkout, CLI commands, release manifest, design, testing, roadmap, and update targets exist.
- The documented commands match `packages/cli/src/main.ts`: `setup`, `doctor`, `install`, `uninstall`, and `mcp`, with `--host codex`.
@@ -1,54 +0,0 @@
# Agent Note: Agent standing orders and repository governance
Status: implemented
## Problem
This repository is about to be developed as a product (distilly) primarily by coding agents. Prose conventions in CONTRIBUTING.md did not load into every session, did not record rejected alternatives, and CI watched `main` while the default branch was `dot-skill`, so the published tree had no running gate. Without a small, mechanical governance layer, agents will re-litigate design, grow standing docs without a home, and treat the skill directory as the product root.
## Decision
The `distilly` branch carries a governance layer sized to what this repo can enforce today: standing orders that load into every session, decisions recorded with what they defeated, and gates that only check what a machine can settle.
- Root [AGENTS.md](../../../../AGENTS.md) holds standing product and process orders. `CLAUDE.md` is a symlink to that file.
- [docs/AGENTS.md](../../../../docs/AGENTS.md) defines documentation tiers and one-home-per-fact placement.
- [docs/architecture.md](../../../../docs/architecture.md) is the live-tree map. The product contract is the in-force design parent, now [docs/design/system-v3.md](../../../../docs/design/system-v3.md). Cookbooks hold steps. Agent Notes hold rationale.
- Agent Notes live under `.agents/notes/{lifecycle}/{class}/` with real dates, unique status and section structure, non-empty required sections, and cross-lifecycle ownership checked by [scripts/verify_agent_notes.py](../../../../scripts/verify_agent_notes.py). A verified-base diff also requires a changed Note for governed paths.
- Skills under `.agents/skills/` describe workflows (doc placement, pre-push evidence, PR review). Contracts stay in docs so an agent that skips a skill still sees the rule.
- [scripts/verify_docs.py](../../../../scripts/verify_docs.py) owns portable Markdown links and endings. [scripts/sync_design_chapters.py](../../../../scripts/sync_design_chapters.py) generates the topic chapters from each canonical design parent, one chapter per numbered section.
- `tools/`, `scripts/`, `prompts/`, and `tests/` carry local Agent instructions. Every local `AGENTS.md` has a sibling `CLAUDE.md` symlink, and the docs gate enforces that both host names expose the same rules.
- Root instructions are branch-neutral and route by the checked-out branch. Shared discovery files and the PR template must also land on GitHub's default branch; a product-branch-only copy is not visible to every clone or new-PR form.
- CI on `dot-skill`, `distilly`, and `main` runs governance once, compile and fail-closed unittest discovery on Python 3.9/3.11, and blocking Ruff. PR checks use the exact metadata base/head range; push checks use the event before/head range. Local contributors run the narrowest matching commands; they do not owe a full-matrix rehearsal.
- [docs/development.md](../../../../docs/development.md) owns the task handoff contract. Handoffs carry transient progress and evidence in the task or PR; Agent Notes remain durable decisions.
- Local history is organized by complete, reviewable feature or bug-fix slices. The coordinating/root agent defines acceptance checks before coding, integrates implementation, tests, generated artifacts, current-state docs, and necessary Note changes, then creates one local commit before another independent feature starts. Subagents contribute only through the shared worktree; local commits require no extra permission, while every push or PR remains opt-in.
- `.githooks/pre-push` provides cheap local governance, lint, and whitespace feedback when a clone enables the tracked hooks. It requires a clean checked-out head so tree gates inspect the committed tree, compares it with the explicitly configured feature-branch base or existing persistent-branch tip, and rejects persistent deletion/non-fast-forward updates. CI is a detection backstop; the [branch-protection procedure](../../../../docs/cookbook/protecting-governed-branches.md) owns remote enforcement.
This is docs-first in the narrow sense: a governed change has a note in the same PR; a semantic reviewer can require one for an otherwise exempt path; unbuilt work may land as `proposed/` before code; implemented notes stay present-tense with the code.
## Alternatives considered
- **Keep CONTRIBUTING.md as the only process doc** — rejected: agents do not reliably load it, and it cannot record alternatives or fail CI.
- **Open with a maximal gate set** — rejected: this tree is still a Python skill plus scripts, so a gate suite sized for a finished product would fail on content we do not own yet and would train `--no-verify`.
- **Put the standard only in SKILL.md** — rejected: an agent editing docs without invoking the skill would miss the contract. The contract lives in docs, the workflow in skills.
- **Leave CI on `main` only** — rejected: the default branch is `dot-skill`. A gate that never runs is not a gate.
- **Keep hand-copying design chapters** — rejected: correct prose with a broken relocated link already escaped review. Generated projections make the parent the only writable source.
- **Store every task handoff as a committed Markdown file** — rejected: transient progress goes stale and becomes a second current-state source. The task or PR carries the handoff; durable decisions and behavior retain their existing homes.
- **Commit each subtask or agent contribution separately** — rejected: those commits preserve orchestration history that cannot be independently built, verified, reviewed, or rolled back as product behavior.
- **Accumulate multiple independent features and commit them together later** — rejected: the combined change cannot be reviewed or rolled back feature by feature, and decision ownership becomes ambiguous.
## Consequences
- Agents opening this branch get standing orders without a human pasting a checklist.
- Product decisions that are not yet code live in `proposed/` and can be reviewed before implementation.
- Note tree, governed-diff ownership, document portability, generated chapter drift, Ruff errors, compile failures, and tests produce a failing CI result. Branch protection must require that result before it becomes a merge/push block.
- Contributors still use unittest and compileall for tool behavior; governance does not replace those tests.
- Local history has one independently reviewable commit per completed feature or bug fix, without contribution-level or WIP commits between feature boundaries.
- A fresh agent can recover authority, actual evidence, remaining work, and workspace ownership without the previous chat.
## Verification
- `python3 -B scripts/verify_docs.py`
- `python3 -B scripts/verify_agent_notes.py`
- `python3 -B scripts/verify_agent_notes.py --base origin/distilly --head HEAD --range-mode merge-base`
- `ruff check tools scripts tests`
- `python3 -B scripts/run_tests.py`
@@ -1,35 +0,0 @@
# Agent Note: Design corpus in-tree and a written review contract
Status: implemented
## Problem
The first governance PR compressed the product conversation into an 80-line architecture map. An agent that only loaded standing docs could not reconstruct locked mechanisms (SDK signatures, seven host pitfalls, profile facets, landing order). Review had a Note gate and no written review contract, so a green CI could ship a design violation.
## Decision
The uncut target design lives in a `docs/design/system-v*.md` parent. [scripts/sync_design_chapters.py](../../../../scripts/sync_design_chapters.py) generates its sections into a sibling `v*/` folder for topic loading and rewrites relocated relative links; each parent is a `Corpus` entry in that script. Chapters are not hand-edited; the docs gate rejects drift. This landed with the v1.1 parent. The in-force parent is now [system-v3.md](../../../../docs/design/system-v3.md); superseding a design adds the next parent and leaves V2/V1 deprecated in place. The registry validates unique version/parent/chapter/output paths and exactly one in-force corpus before writing.
`docs/` is now graded by job: `design/` (contract), `architecture.md` (live tree), `cookbook/` (steps), `process/` (review), `testing.md` (what green means).
Review splits in two: the contract in [docs/process/code-review.md](../../../../docs/process/code-review.md), the walk in [distilly-code-review](../../../skills/distilly-code-review/SKILL.md). Blockers include semantic prose review, docs matching code, design-corpus match, required evidence, and the distilly-specific list (no work/persona split, no ingest+commit merge, no global persona files, no silent trim, no O(n²) commit). Review scope stops at the checks this tree actually runs: the contract points at the command list rather than naming a gate no command produces.
## Alternatives considered
- **Keep architecture.md as the only spec** — rejected: the user required the full design, and the compressed map had already dropped the capability inventory, the value types, and the injection pitfalls.
- **Design as one file with no chapter split** — rejected: agents implementing one seam need to load that section. The parent remains canonical.
- **A maximal review checklist covering every gate we might ever want** — rejected: demanding evidence that no command in this tree can produce trains reviewers to skip the checklist entirely.
- **Put review rules only in the skill** — rejected: an agent that skips the skill must still see the contract.
## Consequences
- Product sessions start at `docs/design/README.md`, not at architecture.md.
- Changing a locked item without a new note is a review blocker.
- PR review has a named walk. Format-green is not a design review.
- Target contracts and shipped state use separate labels and owners; design text cannot be cited as proof that code exists.
## Verification
- `python3 scripts/sync_design_chapters.py --check`
- `python3 -B scripts/verify_docs.py`
- Semantic review of [docs/process/code-review.md](../../../../docs/process/code-review.md) against the root standing orders
@@ -1,45 +0,0 @@
# Agent Note: Design prose states our own conclusions, and publishing is opt-in
Status: implemented
## Problem
Two properties of this repository constrain how design material may be written and where it may live.
It is public, and the published skill sits on the default branch, so anything that reaches a branch here is readable by anyone who clones it. A design document is therefore a product surface, not a private working note.
And branch history is not a retraction mechanism. Once a pull request targets a branch, its head and merge objects stay retrievable under `refs/pull/<n>/*` however the branches later move, so a rewrite cannot pull material back. Any control that depends on "we can rewrite it later" does not exist.
Without written rules for both, design prose drifts toward recording where a mechanism sits in the landscape instead of why it is correct here, and drafts reach a public branch before anyone decided they should.
## Decision
**Design prose states the conclusion, the mechanism, and the failure it prevents, in our own terms.** A row that cannot state its own reason is incomplete. Where a comparison is load-bearing it names a category — "a memory service that carries its own model credentials" — never a product. Integration targets and dependencies are still named, because an adapter cannot be implemented against an anonymous host.
The test is whether an implementer can act on the row alone. "This mechanism is standard practice" fails it; "one row per subject, so a worker that falls behind upserts to the latest state and dedupes for free" passes, because the reason names the failure it avoids. All design parents follow this rule: in-force [system-v3.md](../../../../docs/design/system-v3.md) and deprecated [system-v2.md](../../../../docs/design/system-v2.md) / [system-v1.md](../../../../docs/design/system-v1.md) are equally public even though only V3 is authoritative.
**Publishing is opt-in**, stated at the top of the root [AGENTS.md](../../../../AGENTS.md). The `distilly` branch is local, and pushing it requires an explicit request. `dot-skill` holds only the published skill. Review happens on the local branch and in the working tree; a pull request is the last step, not the medium.
## Alternatives considered
- **Let each chapter choose its own citation style** — rejected: the rule has to be uniform to be checkable, and the mixed result reads as an inventory rather than a specification.
- **Hold the deprecated parent to a weaker standard** — rejected: the archive rule protects decision history from silent revision, not public prose from a correction the whole tree needs. A reader cannot tell which parent is authoritative from the prose style.
- **Delete the deprecated parent instead of holding it to the standard** — rejected: the alternatives it records still explain why several current conclusions hold, and the in-force design cites it as its predecessor.
- **Apply the rule to design documents only, not to Agent Notes** — rejected: notes are published too, and a rejection reason phrased as an attribution is weaker than the requirement it actually encodes.
- **Push freely and rely on a later history rewrite** — rejected: a rewrite cannot reach a merged pull request's objects, so not publishing is the only control that holds.
- **Keep a private mirror for drafts** — rejected: two remotes for one branch guarantees they diverge, and the local branch already serves the purpose.
## Consequences
- A design row that cannot state its own reason is incomplete; naming a source does not repair it.
- Pushing `distilly` requires an explicit request. An agent that opens a pull request against `dot-skill` publishes permanently.
- Category comparisons stay legal, so a chapter can still explain why a whole class of approach was excluded.
## Verification
- Each adopt/exclude row in the storage chapter states a reason an implementer can act on without further lookup.
- `python3 -B scripts/sync_design_chapters.py --check`
- `python3 -B scripts/verify_docs.py`
- `python3 -B scripts/verify_agent_notes.py`
- `python3 -B scripts/run_tests.py`
- `git ls-remote origin` shows no `distilly` ref
@@ -1,25 +0,0 @@
# Agent Note: One dedicated Note per reviewable feature
Status: implemented
## Problem
The governed-diff gate accepted any modified Agent Note. A new feature could therefore append implementation decisions to a long-running aggregate Note, turning “one home” into one giant document and preventing the feature from being reviewed or moved through its lifecycle independently.
## Decision
Every reviewable feature commit owns one concise dedicated Note. Aggregate Notes retain overall direction and link to feature Notes rather than repeating their rationale. A governed range is satisfied only by an added Note or by renaming the same Note from `proposed` to `implemented` or `rejected`; modifying an existing Note or renaming it within one lifecycle is insufficient. Semantic review still rejects multiple features hidden behind one Note because path inspection cannot infer product scope. Existing historical Notes are not reorganized by this rule.
## Alternatives considered
- **Ban only the two current aggregate Notes** — rejected because the same failure would recur under a new filename.
- **Require exactly one changed Note per PR range** — rejected because one push or PR may contain several already-separated feature commits.
- **Infer feature count from packages or directories** — rejected because one vertical slice legitimately crosses implementation, tests, generated files, and documentation.
## Consequences
Follow-on bug fixes and governance changes create their own Note instead of rewriting an earlier owner. A proposal may still be written before implementation and then lifecycle-renamed in its feature commit. The gate proves fresh path ownership, not Note quality or exact feature count, so semantic review remains required.
## Verification
Verifier fixtures reject a governed diff accompanied only by a modified Note or same-lifecycle rename and accept an added Note plus both allowed proposal lifecycle transitions. The focused 24-test suite, Ruff, compile, Markdown formatting, documentation links, Note structure, and whitespace checks pass.
@@ -1,39 +0,0 @@
# Agent Note: Single-writer SQLite storage authority
Status: implemented
## Problem
The unreleased Engine treated many files as independently authoritative while several product processes could write them. Preserving one mutation across those files required mutation-specific journals, staging, recovery branches, layered locks, projection markers, and full-history validation on ordinary reads. That machinery grew faster than the distillation product.
## Decision
Each `DISTILLY_ROOT` has one local Engine writer. SQLite/WAL is the sole structured transaction authority; normalized material, raw, and other large immutable bodies use a content-addressed blob store; Markdown, prompt, Library, queue/search views, plugin files, and human-readable JSON are rebuildable projections or exports. Each business mutation has one SQLite transaction.
One Engine-private in-memory blob access gate protects both pre-reference puts and active blob reads; GC runs only with its exclusive maintenance lease and after rechecking database references and pins. Complete backup/restore uses a separate, narrow Engine administration contract and CLI path because switching an entire authority is not a subject business mutation.
The contract changes in this feature do not claim the package-internal file implementation has migrated. Later vertical feature commits replace one live method path at a time and remove its corresponding old mechanism in the same commit.
## Alternatives considered
- Continuing file authority was rejected because each mutation added another journal, recovery validator, staging protocol, and crash matrix without product capability.
- Keeping several direct writers over SQLite was rejected because it preserves cross-process coordination and adds another medium to reconcile.
- Putting large/raw bodies in SQLite was rejected because immutable content-addressed files are simpler to deduplicate and stream; SQLite owns their references and visibility.
- A generic storage provider was rejected because the product needs one reliable local implementation, not speculative backends.
- Migrating the unreleased V3 file layout was rejected because no public runtime or user V3 store exists; only the published legacy skill remains a real import source.
## Consequences
The target deletes direct storage writers, per-mutation TransactionRecords and recovery/staging protocols, business file-lock hierarchies, Library intent/dirty reservation, queue transaction simulation, full-history hot reads, and public persistence schemas. Claim/evidence, incremental distillation, deterministic ids/rendering, immutable versions, review/correction/rollback, provenance, RequestId replay, privacy, exports, backup/restore, and complete audit remain product requirements.
No surface may import storage or write the root. Projections expose source generation/LSN and fail stale; doctor, restore, and bundle import retain exhaustive audit. The current paused Step 11a journal/staging/recovery work is not part of the target and remains uncommitted pending its later correction vertical slice.
## Verification
- `python3 -B scripts/sync_design_chapters.py`
- `python3 -B scripts/verify_docs.py`
- `python3 -B scripts/verify_agent_notes.py`
- `python3 -B -m unittest tests.test_sync_design_chapters tests.test_verify_docs tests.test_verify_agent_notes`
- `ruff check scripts/sync_design_chapters.py tests/test_sync_design_chapters.py`
- `git diff --check`
- Independent `distilly-code-review` rounds found no P0; every reported P1 was corrected before the feature commit.
@@ -1,57 +0,0 @@
# Agent Note: distilly product, SDK, profile, and host injection
Status: proposed
## Problem
`dot-skill` distills people into Claude-oriented skill files, but the product we want is a personal-memory engine: many subjects (including self), objective distill, version lineage, user corrections, a thin Agent SDK, and host-specific injection. The current `work.md` + `persona.md` split cannot hold family, public figures, or fictional characters without forking templates. Five runtime methods are not a product contract, and thirty flat methods are not an SDK.
## Proposal
Ship distilly as an independent product path on this repository (later a rename). Current full contract: [docs/design/system-v3.md](../../../../docs/design/system-v3.md). Live-tree map: [docs/architecture.md](../../../../docs/architecture.md).
The product rules below still hold as refined by [the V3 supersession note](2026-08-20-design-v3.md). The implementation language, package cut, async API, and error model in this note's original Python form do not: TypeScript first replaced them in V2, while V3 is now the in-force productized contract. [system-v2.md](../../../../docs/design/system-v2.md) and [system-v1.md](../../../../docs/design/system-v1.md) are deprecated history. Storage authority has one separate owner: [single-writer SQLite storage authority](../../implemented/simplification/2026-08-21-single-writer-sqlite-storage-authority.md).
Locked product rules (do not re-open in implementation PRs without a new note):
- Subjects include colleagues, relations, celebrities, fictional characters, and `self` on one model. `self` is a normal id.
- Distill is evidence-bounded. Material-set hash unchanged → skip. External model proposals may vary; engine hashing, evidence resolution, patch application, rendering, and transaction semantics are deterministic. Explainable mechanical risks suspend the candidate (`promote` / `reject`).
- Default zero extra API key. Host `ingest` → `pending(brief)` → claim-only `commit`. No required multimodal or embedding key. Unparsed media is not distill input.
- Client: `Distilly` + `Person`. Capability groups stay internal. MCP exposes `get`, `ingest`, `pending`, `commit`, `correct` only.
- Two adapter kinds: `SourceAdapter` (collection; v1 main path is host `ingest`) and `HostInjector` (v1 required).
- Three load paths: `prompt`/`get` (this spawn, full text), `install` (host skills), `export` (one identity file). Temporary personas never write global instruction files.
- Profile = closed core + open domains + evidenced claims. Domain packs replace colleague/celebrity as types. Default create kind is `person`.
- Graph v1 is relations only. Insert node O(1), attach relations O(k), no O(n²) rebuild. Pending mentions are not auto-linked.
- Bot is a binding that pins one subject and version. It must not invent its own persona files.
- The plugin ships manifests and skills over a versioned local runtime, with a local evidence/review Panel in the first usable release. No hosted MCP server and no OAuth login. Acceptance: distill a public figure and `get` without login.
Target storage authority and package layout are in V3 chapters [06](../../../../docs/design/v3/06-storage-authority-and-transactions.md) and [25](../../../../docs/design/v3/25-package-and-source-tree.md). Existing `PRIMARY_ARTIFACTS` migrate into core and domains; do not keep work/persona as the top split.
## Alternatives considered
- **Remain a Claude skill only** — rejected: bots, Codex, LangGraph, and a marketplace cannot share a skill directory as the source of truth.
- **Requiring four API keys for model, embedding, multimodal, and rerank** — rejected: the default path must run on the host model the user already pays for.
- **Episode or vector memory as the product model** — rejected for v1: evidenced claims and immutable versions are the semantic truth, while retrieval indexes remain derived.
- **Flat seven-group Client** — rejected: a small entry object survives capability growth; the groups remain internal modules.
- **Global AGENTS.md as the load mechanism for ten temporary agents** — rejected: one process-wide file cannot isolate personas and pollutes the repo.
- **Salience truncation in v1** — rejected: first version injects the full profile; adapters may fail closed if the host truncates.
- **Personality affinity edges in v1** — rejected: relations are evidenced; similarity is a later derived index.
- **Calling the evidenced edge a "statement edge"** — rejected by product language. The user-facing name is relation (`Relation`).
## Acceptance criteria
- A host agent can `ingest` text or files, acquire a complete `pending(brief)`, submit a claim-only `commit`, `get` / `prompt`, and `correct` without an extra LLM key.
- `prompt()` text can be placed in a Claude Task or Codex child instructions without writing repository instruction files.
- A mechanically risky candidate does not replace `current` until `promote`.
- A new `SourceAdapter` can register without changing `Person` methods.
- Relation `link` + `neighbors` work without scanning the whole graph on commit.
- Governance checks in the [governance note](../../implemented/process/2026-08-19-agent-governance.md) stay green.
## Risks
- The live tree still emits work/persona artifacts. The later legacy-import feature must preserve real published inputs without extending that split into the new product.
- Full-profile inject will hit context limits; v1 fails visibly rather than inventing a silent trimmer.
- Collection adapters will rot on host UIs. That rot stays in delegated plans or community packages, not in `Person`.
- Renaming the GitHub repository and telemetry env vars is still open and will touch every install doc.
- Earlier capability and public-class drafts showed different `create` and `commit` signatures. V3 §8 and §18 freeze one atomic ingest/create path and one claim-only commit shape, with actor, ids, quality, and Markdown engine-owned.
- V3 keeps direct correction as a deterministic engine path that calls no model; MCP-relayed correction suspends for user confirmation. Relations remain a typed additive post-core slice with a rebuildable projection rather than a pretend first-release method.
@@ -1,63 +0,0 @@
# Agent Note: reserve the watch and local Panel seam
Status: proposed
## Problem
V2 had four faces but only two of them were specified. The model tools, host plugin, and bot binding each got a chapter; the Panel got two sentences saying a tool returns a loopback URL, and a TUI was never mentioned. That left an unwritten interactive interface which the first product code would have guessed at.
The guess is expensive in one specific way. Everything specified so far is request/response: a call arrives, a result goes out, the process moves on. A TUI and a panel stay open while background work changes the same data underneath them. Adding change notification later means changing `EngineClient`, the single transport seam every face shares, so a late addition touches all of them at once.
Two smaller gaps follow from the same omission. The panel runs in a browser, so it is the first out-of-process caller, and nothing said what its transport or its authorization is — a local HTTP server that drives the engine is reachable by any process and any web page on the machine. And nothing said who owns the numbers a screen displays, which is how two interfaces end up reporting two different maturity values for the same subject.
## Proposal
V2 originally specified both surfaces and deferred both. [Design V3](2026-08-20-design-v3.md) supersedes that landing choice: [the local Panel](../../../../docs/design/v3/15-local-panel.md) is required in the first usable release because evidence and suspended candidates need a trustworthy product surface; TUI remains a later EngineClient consumer and gets no empty first-release package. The `watch`, aggregate ownership, loopback transport, and security decisions below are retained.
Storage and process authority have one separate owner: [single-writer SQLite storage authority](../../implemented/simplification/2026-08-21-single-writer-sqlite-storage-authority.md). This note retains only the decision that Panel is an EngineClient-only face with content-light invalidation and engine-owned aggregates.
Panel and a future TUI read the same protocol-owned aggregates through EngineClient. Their rendering medium does not grant either a second fact path.
Decisions, each with the failure it prevents:
- **`EngineClient.watch` is a required member, and its events are re-read signals.** The payload locates a change (which subject, which version) and carries no profile content; the consumer re-reads. A display cannot become a second source of truth: every event corresponds to a mutation already committed by Engine. `kind` is an extensible union, so a consumer that meets an unknown kind re-reads rather than crashing or discarding.
- **Required, not optional.** The in-process implementation emits after a successful commit and costs nearly nothing. An optional capability would make every consumer write the same "does this client support watch" branch.
- **The panel is `EngineClient` over loopback HTTP**, with `/rpc` carrying the identical method names and `/events` carrying `watch` as Server-Sent Events. No second protocol and no second write path: the panel never opens Engine persistence directly.
- **Four panel-server refusals are security invariants, not configuration**: bind `127.0.0.1` only, require a per-run token, reject cross-site `Origin`, and exit when the port is taken instead of silently choosing another one — a silently moved port makes the address already printed to the user point somewhere else. `/rpc` input is validated as untrusted JSON at the same boundary as model input; being our own front end earns no exemption.
- **Interfaces render and never derive product facts.** Quality summary, maturity, and facet coverage are read from engine aggregates. A screen that computes its own completeness percentage gives the product a second answer to a question the engine already owns.
- **A screen's aggregate is an engine read method.** Any new aggregate enters the capability inventory and the facade first, then both interfaces consume it. There is no interface-only package to hold shared view logic, because such a package starts computing values and the two renderings then disagree.
- **The Panel is a deletable leaf.** Its server and web entries depend on protocol/EngineClient, and CLI supplies the local runtime and built assets. It never imports engine stores. A future TUI must follow the same rule when a real slice justifies the package.
Landing order places in-process `watch` with the commit path, then ships the Panel with the first complete plugin slice. Protocol contract fixtures prove aggregates before the HTTP UI; a speculative TUI is not used as scaffolding.
The implemented Step 10 rationale lives in the [dedicated verified review and Panel feature Note](../../implemented/feature/2026-08-21-step-10-verified-review-and-panel.md).
## Alternatives considered
- **Leave the Panel at “a tool returns a URL”** — rejected: the first interactive caller would either poll or reach into the engine, and both are far more expensive to undo than to specify.
- **Polling instead of `watch`** — rejected: it cannot say what changed, and its interval becomes a tunable that is wrong on both sides — too slow to feel live, too fast to be cheap.
- **Make `watch` an optional capability on `EngineClient`** — rejected: every consumer would carry a capability check, and the in-process implementation is nearly free, so the branch buys nothing.
- **Put profile content in the event payload** — rejected: the payload becomes a second copy of state that can disagree with Engine reads, and it grows the transport for every subscriber whether or not the screen is showing that subject.
- **A `@distilly/view` package with shared view models** — rejected: shared *types* already belong in protocol, and a package for shared view *logic* would start deriving numbers, which is exactly the divergence the render-only rule prevents.
- **Build a TUI first only to prove aggregates** — rejected by V3: protocol and integration fixtures prove the contract without adding a product surface that does not enter first-release acceptance.
- **Serve the panel from the panel package's own binary** — rejected: two executables to install, and the server is a thin layer over `EngineClient` that belongs with the other entry points.
- **Skip the token because the server binds loopback** — rejected: loopback stops remote hosts, not other local processes and not a malicious page in the user's browser, which can post to `127.0.0.1` freely.
- **Append the chapter at the end to avoid renumbering** — rejected: delivery surfaces belong together, and agents load this contract by chapter, so a misfiled chapter costs more over time than a mechanical renumber the link gate verifies.
## Acceptance criteria
- `EngineClient.watch` returns an unsubscribe function, and every emitted event corresponds to a write already present in lineage or queue state.
- A consumer handling an unrecognized event kind re-reads instead of throwing, proven by a test that emits a kind the consumer does not know.
- The panel server refuses a request with no token, a wrong token, and a cross-site `Origin`, and exits non-zero when the port is occupied — four tests, each observed red before it passes.
- A panel write and the equivalent CLI write produce the same version and the same lineage entry, because both pass through one commit path.
- Panel server and web do not import engine stores; the dependency gate fails when that is violated.
- Every value the Panel displays is traceable to an EngineMethodMap result; the Panel computes no quality, maturity, coverage, or review reason of its own.
- Deleting `@distilly/panel` leaves core engine, runtime, SDK, and protocol compiling; the first usable plugin release itself still requires Panel acceptance.
## Risks
- The Panel adds real first-release scope. V3 limits it to Library, Subject, Review, and Settings/Doctor over existing MethodMap aggregates; TUI and remote Discover stay out until a real slice exists.
- A required `watch` obliges every future transport to carry a subscription. Server-Sent Events covers the HTTP case, and a transport that genuinely cannot subscribe would be a new locked-item argument, not a silent fallback.
- Event volume during a batch distillation could flood a subscriber. Because events are re-read signals with no content, a consumer may coalesce them per subject; that is a consumer concern and needs no engine-side buffering yet.
- The token lives in a URL fragment, which keeps it out of server logs and the `Referer` header but leaves it in the user's browser history on that machine. It is per-run, so a stale token authorizes nothing.
- V2 chapter numbers remain historical; current authority links are mapped by topic to V3, and the link gate covers every repository reference.
@@ -1,58 +0,0 @@
# Agent Note: distilly product code is TypeScript
Status: proposed
## Problem
The approved v1 design specified the product in Python: `src/distilly/api.py`, synchronous signatures, and `models.py` living inside the client package. Three of the four faces the product must serve are Node hosts — the Claude Code plugin, the Codex plugin and its MCP App surface, and the later panel. Keeping the engine in Python forces the protocol to be written twice, and the two copies will drift on exactly the field names v1 promised to freeze. The Python signatures are also synchronous, which blocks a host event loop when the same calls run inside an MCP server.
There was no runnable user product or `~/.distilly/` installation when this language decision was made. The TypeScript workspace could therefore replace the unshipped Python product design without migrating user data or preserving two production implementations.
## Proposal
Product code is TypeScript. V2 first introduced that contract and deprecated V1; [docs/design/system-v3.md](../../../../docs/design/system-v3.md) now inherits the language decision and is the in-force product contract. [docs/design/system-v2.md](../../../../docs/design/system-v2.md) and [docs/design/system-v1.md](../../../../docs/design/system-v1.md) remain deprecated history. This note owns the language decision; [2026-08-19-distilly-product.md](2026-08-19-distilly-product.md) keeps the product rules, [the V3 note](2026-08-20-design-v3.md) owns later workflow/package refinements, and [the storage authority note](../../implemented/simplification/2026-08-21-single-writer-sqlite-storage-authority.md) owns persistence.
Structural decisions that differ from v1, each with a reason:
- **`@distilly/protocol` owns the shared vocabulary** — branded ids, value types, method names, error codes, and the zod schemas. v1 put `models.py` in the client, which forced the engine to depend upward on its own consumer.
- **`distilly` is a facade package** whose browser-safe root holds `Distilly` and `Person` over an injected `EngineClient`; the future Node composition entry connects to the local Engine service only after a complete runtime exists. A separate thin client package would have had exactly one consumer, so it is not cut.
- **Every public I/O operation returns a `Promise`.** No synchronous twin. Pure `person()` handle construction is the explicit exception; synchronous fs inside an MCP server still stalls the host.
- **Errors carry a frozen `code` union**, because the code is what survives a JSON-RPC or MCP hop; the class name does not.
- **Validation happens only at the eight boundaries listed in V3 design §7.6** — model/RPC input, ingest, brief/commit, claim patch, disk, config, and extension/bundle input. Typed same-process calls get no defensive branches.
- **Branded ids use a `unique symbol` brand**, so `SubjectId` cannot be passed where `VersionId` is required. Python could only ask review to catch that.
V2 froze the three open items that then blocked implementation. V3 supersedes their concrete wire shape without reversing the language decision: create is the discriminated target of atomic ingest, commit accepts only a claim patch after briefing, correction calls no model and MCP-relayed corrections suspend for confirmation, and relations are an additive post-core method extension over a rebuildable graph projection.
Profile layering, claim evidence, version lineage, zero-extra-key default, and the relations-only graph remain. V3 intentionally changes the not-yet-shipped material digest, version transaction, host workflow, and package composition; those changes are owned by the V3 Note, not by this language decision.
`EngineClient` gained one member after this note: [2026-08-19-interactive-faces-seam.md](2026-08-19-interactive-faces-seam.md) specifies the TUI and panel surfaces and adds a required `watch` subscription to the transport seam.
## Alternatives considered
- **Stay in Python (v1 as written)** — rejected: the plugin and panel need a second language anyway, so the protocol and its frozen field names would exist twice, with two gate suites maintained indefinitely.
- **Python engine behind a TypeScript plugin shell** — rejected: it creates two sources of truth and makes every shared product value cross a second language boundary.
- **Rust or Go engine with a TypeScript shell** — rejected: distribution and host plugin ecosystems do not match, while contributor and FFI/RPC cost rises without a demonstrated need.
- **Port only the MCP layer to TypeScript** — rejected: that layer is the thinnest one, so it does not repay a two-language repository, and `commit` validation must live on the same side as the engine.
- **Rewrite `prompts/` into TypeScript string constants** — rejected: the distillation prompts are text assets. The engine reads the files.
- **Delete the published Python skill now** — rejected: `dot-skill` has users. Retirement conditions are in V3 design §28.6.
- **Keep both `@distilly/client` and a facade** — rejected: the client package's only consumer would be the facade.
## Acceptance criteria
- A host agent completes the no-extra-key path through the five MCP tools: resolve, atomic ingest/create, pending brief, claim-only commit, get, and correction.
- `prompt()` text drops into a Claude sub-agent or Codex child instructions without writing any repository instruction file.
- Deleting a rebuildable projection loses no semantic state, and relation lookup does not require an all-history scan.
- `neighbors` and `link` touch no full-log scan, and `commit` performs no all-pairs comparison.
- A mechanically risky candidate leaves `current` in place until `promote`.
- `distilly migrate` converts a real published legacy-skill fixture through the confirmed import path and refuses an unknown source schema/version instead of guessing.
- A new `SourceAdapter` or `HostBinding` registers without changing `Person`.
- Importing the built package entry — not the source — constructs an engine and reads a profile.
## Risks
- The repository is two languages until V3 design §28.6 is satisfied. The mitigation is an expiry rule, not a permanent second lane: the legacy CI job is deleted, never disabled.
- OCR, transcription, and document parsing are stronger in Python. Parsers stay replaceable external processes, and unparsed material never enters distillation, so this stays outside the engine.
- Full-profile injection will hit context limits. The first version fails visibly instead of trimming silently.
- `zod` is a runtime dependency on the protocol boundaries. If bundle size later becomes a measured problem, changing that choice requires a new owned design decision rather than an untracked exception.
- Contributors who only write Python can change `prompts/` and documentation until the migration completes.
- Renaming the GitHub repository and the `DISTILLY_*` environment variables is still open and will touch every install document.
@@ -1,160 +0,0 @@
# Agent Note: productize the host-LLM workflow in Design V3
Status: proposed
## Problem
Design V2 established the right product boundary — local facts, multiple subjects, evidenced claims, version lineage, a zero-extra-key host path, a TypeScript engine, and a small public facade — but its specified plugin path could not execute the product acceptance test from a clean installation.
The five MCP tools had no way to create the first subject, force an immediate distillation boundary, or retrieve the material content needed to construct a draft. Pending jobs exposed only ids, hashes, and counts while hostBriefing existed only as an internal engine method. The draft let an untrusted host submit claims and unrelated Markdown as two competing truths. Evidence validation required a non-empty list but did not require the referenced material to exist, belong to the subject and generation, or contain the quoted text. A 32-bit material digest simultaneously carried identity, deduplication, evidence, and lineage. The plugin manifest assumed a distilly executable was already on PATH, and the local panel was optional even though users had no other product surface for inspecting evidence or resolving a risky candidate.
V2 also made two semantic promises that do not survive the decision to use a host LLM for research and distillation. New evidence can contradict old evidence, so profile quality does not monotonically rise with material count. Two external models can produce different evidence-bounded proposals from the same input; the deterministic obligation belongs to the engine's hashing, validation, patch application, rendering, and version transaction, not to pretending the external sampler is byte-stable.
These gaps cross the product workflow, wire contract, value types, file format, package cut, installation, review surface, and release order. Continuing to patch V2 would leave a historical design whose locked decisions mean two incompatible things. The repository's design-corpus rule exists for exactly this situation: add the next self-contained parent and preserve the old one as deprecated history.
## Proposal
[docs/design/system-v3.md](../../../../docs/design/system-v3.md) becomes the only in-force target contract. [system-v2.md](../../../../docs/design/system-v2.md) and its generated chapters remain readable, are marked deprecated, and are not rewritten to match V3.
Storage authority has one separate owner: [single-writer SQLite storage authority](../../implemented/simplification/2026-08-21-single-writer-sqlite-storage-authority.md). This aggregate note retains the overall V3 workflow, product surface, and release direction.
V3 makes the product chat-first and local-reviewable:
- The host LLM plans research, uses capabilities the host actually has, reads a typed briefing, and proposes a claim patch.
- The local engine owns subject ids, full SHA-256 content digests, material ids, deduplication, generation leases, evidence resolution, claim ids, quality summaries, deterministic Markdown, current versus suspended state, and version transitions.
- The model-facing surface keeps exactly five names: get, ingest, pending, commit, and correct. Ingest carries a discriminated create target and enqueue=now; pending carries list, brief, renew, and release. This closes the workflow without adding create, flush, research, or briefing tools.
- Briefing is the only legal host-distillation input. It atomically acquires a generation lease and returns the baseline claims, complete incremental materials, provenance, short evidence handles, prompt/schema/source-grouping versions, and explicit size limits. A full BriefContract digest is pinned in the lease and echoed by commit, so a runtime upgrade cannot silently reinterpret the host's evidence before quality is calculated. It never silently truncates.
- The host submits add, revise, supersede, and contest claim operations. It does not submit claim ids, actor, confidence, version ids, or Markdown. Claims are the semantic truth; Markdown and prompt text are deterministic projections.
- Commit hard-rejects invalid generation, lease, subject ownership, material membership, quote, locator, and patch shape. A structurally valid candidate that changes identity, loses coverage, introduces conflicts, or disagrees with a correction becomes suspended. Clean candidates become current without a compulsory click.
- The local Panel ships in the first usable release. It provides Library, Subject, Review, and Settings/Doctor pages over the same EngineClient. It renders engine-owned values, never opens persistence, and preserves the loopback, per-run token, Origin, Host, and single-write-path security boundary.
- Setup installs a versioned runtime and an absolute launcher before it writes host manifests. A fresh plugin no longer relies on a pre-existing global executable or an unpinned npx latest invocation.
- Plugin Source, Local Library, and the future remote Profile Catalog are separate concepts. The first release has no account, remote sync, publish API, or Discover placeholder. A remote catalog enters only after the local bundle, provenance, consent, moderation, and import-as-suspended contracts are proven.
MaterialId, full ContentDigest, and ProvenanceDigest are distinct; material identity is not reused as proof of source independence. Versioned normalization and source grouping keep provenance inspectable, collapse derived representations only when evidence supports it, and fail closed rather than inflating diversity.
RequestId is globally idempotent across methods and subjects: the same trusted preimage replays the stable result, while changed method, input, actor, or session authority conflicts. Privacy deletion may retain only the content-free identity needed to prevent a used RequestId from becoming a new mutation.
Privacy purge removes product-visible content and exact replay payloads without reopening a used RequestId. A same-input replay after purge returns not-found while changed input or actor remains an idempotency conflict.
Creation has a wider uniqueness boundary than later writes. The reserved People space is deterministic; exact locators outrank exact canonical display/alias candidates; ambiguity and locator disagreement fail visibly; description never proves identity. RequestId remains `req_` plus 32 lowercase hexadecimal characters.
Automatic queueing uses one auditable `auto-v1` rule: three uncommitted materials or a 30-minute-old uncommitted material, evaluated on ingest. The baseline is the current immutable version membership, and a new generation invalidates an older pending job.
An active lease carries an engine-generated per-client owner, acquisition/expiry times, generation, and the complete BriefContract. Expiry is a read-time predicate; exact brief replay returns the original bounded briefing rather than regenerating it under changed prompt, material, or capacity inputs.
The first grouping and briefing contracts are byte-addressed rather than implementation labels. `source-groups-v1` unions exact raw, private-conversation, artifact/representation locator, fallback source-URI, and content proof keys; component ids hash the sorted unique key set, while diversity eligibility uses only public artifact or fallback source-URI proof and fails closed on access conflict. The prompt version hashes the raw prompt asset together with the exact ordered evidence rules, including the model-facing prohibitions against obeying material-supplied commands or disclosing environment, configuration, secrets, and other subjects. BriefContractDigest hashes exactly the source-grouping, prompt, and draft-schema fields. Host capacity is trusted net session context, not a model parameter and not a fixed subtraction from gross context/result fields: a successful preflight must carry either a trusted handshake that directly reports the current surface's net budget or an exact host/version/environment/release/wire/canonical-skill fixture proven at the real truncation boundary. A complete compact canonical briefing uses a conservative byte-equals-token fixed point and must fit both host budgets, the 4 MiB internal cap, and 999 material references before lease acquisition.
Immutable versions own a complete claim/evidence/material-membership snapshot. VersionId hashes canonical claims with `createdIn` removed, then assigns the resulting id to claims created in that version; this preserves creation lineage without an impossible self-referential digest.
Commit has one public schema and one business transaction. Hard reject changes no product state and preserves the lease; successful current or suspended disposition consumes pending atomically with the immutable version, operation, and events. LeaseOwnerId participates in commit idempotency so a different client cannot replay another session's lease-bound mutation.
Claim application and rendering are also byte contracts. Commit rebuilds its narrow EvidenceContext from the lease-pinned baseline and material membership; it does not depend on retaining or searching a previous brief result. A canonical resolved draft deduplicates and orders evidence and observations, validates time ranges, targets each base claim at most once, and derives ClaimId from `claim-v1` plus subject and resolved draft. Empty patches are valid; add/revise create active claims, revise records the replacement edge, supersede does not invent one, and contest preserves the historical id/text/createdIn while merging evidence. Quality and review reasons use exact set/count/delta rules and canonical ordering, including suspicious/manual gates on a first version. Relations enter additively in their later slice instead of occupying a disabled runtime field.
`profile-renderer-v1` is a literal, injection-safe projection contract: seven core roots stay in fixed order, domains and claims use UTF-8 ordering, active and contested claims are explicit, superseded claims are omitted, and dynamic claim text is carried only inside canonical JSON records. Every section, combined profile, and prompt ends in one LF. The immutable version snapshots its display name and canonical review reasons, so a later subject rename cannot alter historical rendering.
The rest of the Protocol surface is closed with the same rule rather than leaving each entry point to invent validation. Random subject, space, job, lease, lease-owner, event, and capture references carry a named prefix plus 128 bits; content-derived raw, version, claim, and relation ids carry a named prefix plus a full SHA-256. Times use one UTC millisecond representation, host names and facet paths have portable ASCII grammars, and briefing evidence handles are the bounded m001..m999 namespace. Public object schemas reject unknown keys, use safe integers, and share conservative byte and collection limits for model-controlled input. JSON error details are recursively JSON-safe, while subject-creation collisions use a code-correlated typed resolution instead of an undocumented details payload. Empty EngineMethodMap results are explicit null, so in-process, disk operation records, MCP, HTTP, and later RPC do not disagree about how to serialize `void`.
The five MCP descriptors are also Protocol data, not presenter copy. One ordered registry binds each exact name to its title, local-only description, runtime input/output schemas, draft-2020-12 JSON Schemas, and all four MCP hints. Runtime and JSON schemas derive from one source and are snapshotted together. The text deliberately describes only local lookup, storage, leases, validation, commit, and correction: host research remains the outer workflow and is not falsely attributed to a tool. Get and pending actions have closed action-to-result mappings, and unknown future EngineEvent kinds fail decoding, skip handlers, and cause a full read-model refresh rather than leaking an untyped event into consumers.
V3 remains intentionally conservative about abstraction. External variation uses TypeScript interfaces: EngineClient, SourceAdapter, MaterialParser, HostCapabilityBinding, full HostBinding, HostInjector, and DraftProducer. Capability-only bindings own the one authoritative preflight; full bindings additionally create injectors/form renderers and own plugin lifecycle/doctor. Stateless algorithms are pure functions. Single production implementations are concrete services. The public API exports no abstract classes.
The package graph separates the engine from local product composition. Engine depends only on protocol and ultimately exposes a core runtime; `@distilly/runtime` composes parser registries, host bindings, doctor aggregation, and the full EngineMethodMap. File ingest remains engine-owned while runtime supplies only a narrow parser port. A vertical slice uses package-internal composition until every CoreEngineClient method has a real handler; it does not export a partial runtime or install placeholders. MCP and Panel receive actor-bound EngineClient instances and never import persistence. Request ids travel in mutation context, actor and briefing capacity come from the trusted client/session boundary, and every non-user correction receives relayed provenance and is suspended rather than labeling itself as a direct user action.
That boundary also determines the release order. Step 8 lands only the browser-safe injected-client `Distilly`/`Person` facade, the five injected-client MCP handlers, and real stdio transport conformance driven by a test-only full fake client and presenter. The fake child proves bytes, descriptors, error envelopes, and teardown; it is not a local backend, an executable product CLI, or evidence that correction and review work. Step 9 adds only binding contracts/registry, injected-provider Codex and Claude Code capability factories, conservative versioned capacity fixtures, and canonical-skill/release assembly; both concrete entries remain capability-only and report private UI capture unavailable. Recall/install later supplies injector/form-renderer implementations, and production composition alone combines those with real plugin lifecycle/doctor into full bindings. Production `distilly/node`, CLI/MCP composition, setup, and fresh-install therefore wait for a feature that first proves every Core method and full binding method has a real handler, without exporting an intermediate partial runtime. The production CLI keeps brief, editing, renewal, and commit in one foreground client session so LeaseOwnerId remains private to that session; no draft, flag, environment variable, or reconnect token persists owner authority.
The implemented Step 10 rationale lives in the [dedicated verified review and Panel feature Note](../../implemented/feature/2026-08-21-step-10-verified-review-and-panel.md).
MCP has one result and lifecycle rule across all five tools. Input, domain, presenter, and unknown implementation failures are normalized to the narrowest `WireFailure`; genuinely unknown exceptions use the redacted `internal_error` code rather than `adapter_failed` or a generic SDK error. The final Protocol output parser validates the result before the same parsed object is returned as both structured content and JSON text. Review presenters fail closed unless the returned ref and loopback review route exactly match the requested ref. Server name is fixed, version comes from the package/release build source, and EOF, signal, close, and transport error converge on bounded idempotent teardown of the owned stdio transport and MCP server while injected clients and presenters remain borrowed.
Canonical skill distribution is a recursive byte contract rather than a single-file checksum. The source root contains only regular files addressed by sorted UTF-8 POSIX relative paths; every file hashes raw bytes, and a domain-separated canonical file tuple hashes the whole tree. Assembly exact-mirrors that tree into both host roots, prunes stale targets, and writes one schema-versioned release manifest whose version is the MCP package SemVer and whose fixed host entries name the exact platform manifest and skill paths. Platform MCP templates remain source-only Step 12 inputs and cannot appear in an installable target before an absolute launcher has been rendered and smoke-tested.
The canonical skill treats trusted successful preflight plus exact five-tool availability as its entry gate. It stops before get, research, ingest, leases, or simulated fallback when that host/runtime evidence is missing; capability selection later in the workflow only consumes the already accepted result. This keeps a source plugin tree from presenting its instructions as evidence that a production runtime exists before the production-composition slice ships.
Source acquisition and private screen authorization are refined by [the private UI capture Agent Note](2026-08-20-private-ui-capture.md). That follow-on decision preserves the five-tool, host-LLM architecture while separating public research, derived text provenance, engine-owned source grouping, and a strictly consented one-to-one private-message lane.
Review cardinality is explicit: one subject has at most one active suspended target. New material may continue to ingest, but another brief/commit or rollback waits for promote, reject, or a correction that explicitly targets and replaces the candidate. A replacement keeps the current version as its parent/CAS baseline, records the old candidate as its content-derivation edge, rejects that old candidate, and creates the new current or suspended target atomically. Immutable version records retain only their created disposition; current/suspended/historical/rejected status is derived from state and events, so review does not mutate historical version files.
The initial Node support window is closed in this decision at `^22.19 || ^24`. This deliberately admits the two LTS lines exercised by the first CI matrix and rejects Node 23/25, which are EOL, plus Node 26 while it remains Current; the Node project recommends production applications use Active or Maintenance LTS releases ([Node.js releases](https://nodejs.org/en/about/previous-releases)). `22.19` is the frozen oldest fixture, not a claim that every earlier Node 22 build is known broken. Setup preflight, the CI matrix, package metadata, and both host fresh-install fixtures must move together if that support policy changes; it is no longer an implementation-time open question.
Local bundle evidence is also closed. A bundle omits complete private materials but includes the minimum shareable excerpt fact for every exported EvidenceRef. Import re-derives local MaterialIds, revalidates each quote, rewrites references, and suspends the candidate. A profile with dangling evidence is not importable.
The document generator registers V1, V2, and V3 as separate corpora and validates the entire registry before writing any chapter. Parent paths, version-directory pairs, chapter directories, and output files must be unique, and exactly one corpus must be in force. This prevents a mistaken V3 directory from silently overwriting deprecated V2 projections.
## Alternatives considered
- **Continue editing V2** — rejected because the executable workflow requires changing locked wire names and meanings, material identity, draft authority, quality semantics, panel priority, and plugin bootstrap at once. Calling both shapes V2 would hide a major break and erase why the old alternative lost.
- **Add separate create, flush, and briefing MCP tools** — rejected because the user-confirmed product surface keeps five memorable tools. Discriminated actions in ingest and pending close the same workflow without exposing internal service granularity.
- **Let the host submit a complete Profile and arbitrary Markdown** — rejected because evidence can be valid while prose invents unsupported facts. Claim patches plus deterministic rendering leave one semantic truth.
- **Leave relationOperations in DistillPatch behind a disabled feature flag** — rejected because every runtime schema and caller would prematurely own an unusable field, and a future implementation could accidentally accept then drop relation changes. The relation slice adds its discriminant, semantics, and tests together.
- **Render free-form Markdown or rebuild historical prompts from the current SubjectRecord** — rejected because model text can inject structure and later renames would mutate historical output. Canonical JSON records plus a version-time displayName make every stored profile and prompt byte-verifiable.
- **Exclude LeaseOwnerId from commit idempotency** — rejected because the same public params could then replay a mutation bound to another client session's lease. The trusted checksum includes the engine-owned owner even though the owner never appears in public params.
- **Use an LLM judge for confidence and approval** — rejected for the first release. It adds another key/cost path and still cannot guarantee consistency. The engine exposes explainable counts and mechanical review reasons; the user resolves semantic risk in the Panel.
- **Count every MaterialId, URL, OCR result, caption, or transcript as an independent source** — rejected because different material identity does not prove independent origin. Exact and provenance-linked representations remain separately inspectable but collapse into engine-owned, diversity-eligible source groups; unknown origin under-counts rather than inflates corroboration.
- **Require every candidate to be manually approved** — rejected because it turns chat-first maintenance into a click queue. Mechanical-risk candidates are suspended; clean candidates advance automatically and remain reversible.
- **Keep the Panel optional until after the SDK** — rejected because evidence and suspended versions need a trustworthy user surface. A tool-only release can create data the user cannot inspect or recover comfortably.
- **Host a remote MCP backend to qualify for a public plugin directory** — rejected because it would turn a platform distribution limitation into a cloud identity store. Local/repo distribution is the first lane; platform support can later be added in HostInstaller without changing product semantics.
- **Treat a Git repository as the whole installer** — rejected because manifests do not install the engine, resolve PATH, negotiate protocol versions, or make upgrades atomic.
- **Subtract one fixed wrapper constant from advertised host limits** — rejected because MCP returns both structured content and an escaped JSON text representation, while host ceilings may be expressed in tokens, characters, bytes, or file-reference fallback behavior. Only a directly reported net handshake or an exact versioned end-to-end truncation fixture can establish BriefCapacity.
- **Ship Step 9 “full” bindings with unsupported injector/install methods** — rejected because the kind would promise a production composition that cannot exist yet. Capability-only bindings are an explicit registry branch; later slices construct a new full registry only when every required method is real.
- **Use symlinks or a SKILL.md-only checksum to share the canonical skill** — rejected because archives and target platforms do not preserve symlinks consistently, while referenced files and assets are executable instruction context too. Release assembly exact-mirrors one recursive raw-byte tree, prunes stale targets, and records a domain-separated tree digest in the release manifest.
- **Build the remote Profile Catalog in the first version** — rejected because accounts, consent, copyright, moderation, impersonation, takedown, signatures, and update policy would delay validation of the core research-to-recall loop.
- **Use `^22.19 || >=24` as a rolling Node range** — rejected because it silently labels every future major supported before CI and host fixtures qualify it. Supporting only Node 24 was also rejected for the first release because the required CI/host matrix includes the Node 22 LTS line; adding Node 26 later is an explicit support decision, not a semver side effect.
- **Case-fold/fuzzy-match names or silently create alongside one exact name candidate** — rejected because both choices can merge or split identities without evidence. Exact versioned labels, locator disagreement, and typed remediation keep the ambiguity visible.
- **Allow arbitrary RequestId strings and normalize them independently per boundary** — rejected because it creates multiple identities for one retry. One strict 128-bit hex wire form is portable and stable across transports.
- **Keep other branded ids opaque and let each package choose regexes and limits** — rejected because ids cross protocols, URLs, exports, and transports. Divergent acceptance would make a value valid in one client and unreachable in another; one portable wire grammar and one conservative limit table are cheaper than compatibility repair.
- **Use `void` in EngineMethodMap and special-case it per transport** — rejected because JSON cannot represent undefined as a required result value. Explicit null is the smallest language-neutral result and facades may still discard it for ergonomic `Promise<void>` methods.
- **Hand-write MCP runtime schemas, JSON Schemas, titles, and annotations in the server package** — rejected because tools/list could drift from the types accepted by handlers. A single Protocol registry keeps model-facing copy local-only, schema dialect and hints reviewable, and snapshots meaningful.
- **Publish a fake-backed CLI or renamed partial workflow runtime with Step 8** — rejected because any production object typed as EngineClient promises the complete MethodMap. Returning unsupported for unimplemented methods only moves the partial-runtime failure from startup to a user call; the stdio fake remains a test fixture and no executable product entry is published until Core closure.
- **Split CLI brief and commit into independent processes and serialize LeaseOwnerId in the draft** — rejected because owner is trusted session authority, not a bearer token. A second connection receives a new owner and must conflict with the existing lease; the first CLI contract therefore keeps brief, renew, edit confirmation, and commit in one foreground session.
- **Return MCP failures through generic SDK/JSON-RPC errors or independently construct text and structured results** — rejected because callers would need two failure contracts and the two representations could drift. Every product outcome is a Protocol-parsed ToolOutput, with its JSON encoding as the only text content; only an inability to serialize even a valid WireFailure remains a protocol-level failure.
## Acceptance criteria
- Design V3 is self-contained: a first-time implementer can find the product journeys, trust boundaries, five wire schemas, value ownership, durability model, SDK methods, package tree, interfaces, pure functions, concrete services, Panel contract, installer, security constraints, migration, and release gates without consulting V2.
- The design generator creates all V3 topic chapters from the parent, marks V1 and V2 deprecated, marks only V3 in force, and fails before writing when corpus paths or outputs collide.
- HostPreflight is a closed success/failure union: success carries structured-tool capabilities, one net BriefCapacity, matching host/version/surface evidence, and warnings; failure carries no capacity/evidence and one non-retryable host_unsupported error. Codex and Claude Code capability factories consume only an injected unknown provider payload, parse and match it at runtime, report private UI capture unavailable, and never probe a user's host installation in Step 9.
- The canonical skill requires that trusted successful preflight and the exact five-tool runtime before its first get or research step; an unavailable runtime, failed preflight, false structured-tool capability, or missing tool stops without simulation, shell fallback, or global instruction writes.
- HostRegistry accepts only discriminated capability/full bindings, rejects every duplicate HostName without mutation, and lists immutable entries by UTF-8 HostName order. Injectors and form renderers have no independent registration path, and no concrete full binding exists before Recall/install plus production setup close all its methods.
- The canonical skill digest covers every regular file recursively using sorted UTF-8 POSIX paths and raw bytes. Assembly rejects symlinks, exact-mirrors and stale-prunes both host copies, writes the fixed schema-version-1 release manifest with one SemVer source and exact platform paths, and excludes unrendered MCP templates from every installable target.
- Every current authority link in root instructions, documentation maps, review guidance, development/testing guidance, and active Agent Notes points to V3 by topic. Historical statements that V2 introduced TypeScript remain historical rather than being blindly rewritten.
- After correction, Panel presentation, Core closure, and production composition exist, a fixed host fixture can execute a clean-store, no-extra-key path through exactly five MCP tool names: get(resolve/profile), ingest with create and enqueue=now, pending(brief), claim-only commit, and correct. The earlier Step 8 fake-client stdio fixture proves only adapter and transport conformance.
- Briefing contains the complete incremental generation and fails explicitly when too large. A lease acquired by one client cannot be acquired by another, expires recoverably, and becomes stale when new material arrives.
- Lease owner and full BriefContract are authoritative product state; same-request replay returns the exact stored briefing or conflicts when owner/capacity changes.
- Lease renewal preserves the same BriefContract digest; commit after a runtime upgrade either uses the pinned source-grouping/draft contract or fails explicitly when that implementation is unavailable. The same patch under different contracts cannot collide on VersionId.
- Commit rejects an unknown, cross-subject, out-of-generation, or quote-mismatched evidence reference and never writes a candidate for rejected input.
- Commit rebuilds its EvidenceContext and m001 mapping from the authoritative base/material membership under the lease-pinned algorithm, not from a brief-result lookup; 65,536 canonical patch bytes pass and 65,537 fail with zero writes, while invalid target/date/locator/evidence and unavailable pinned algorithms return their exact error codes without consuming pending or lease.
- A valid clean candidate becomes current; an identity, coverage, voice-example, contested-claim, correction, or source-diversity risk becomes suspended while the old current remains readable.
- The accepted claim-only patch has byte-stable empty/add/revise/supersede/contest semantics, canonical evidence and observedIn ordering, one operation per base claim, exact `claim-v1` ids, exact strength/quality counts, canonical gate reasons, and no relationOperations before the later relation slice.
- Claims, quality summary, Markdown, prompt, version, event, and current pointer are engine-derived and reproducible from the same accepted patch and authoritative snapshot.
- Commit success atomically consumes pending/lease, emits the version event followed by job.changed, and exact replay cannot transfer across LeaseOwnerId.
- `profile-renderer-v1` has fixed core/domain/status ordering, canonical-JSON dynamic records, injection fixtures, and one trailing LF for every artifact. Profile.displayName equals immutable VersionRecord.subjectDisplayName and historical profile/prompt bytes survive a later SubjectRecord rename.
- Each version has one complete claims/evidence/material-membership snapshot; VersionId derivation has no `createdIn` cycle.
- Different URLs with identical content remain different MaterialIds but share a source group; captions/OCR/transcripts derived from one artifact share a group; unknown provenance does not increase eligible diversity; grouping is order-independent and versioned with QualitySummary. Critical provenance changes produce distinct MaterialIds, while each version's material manifest makes its historical grouping reproducible.
- Every EngineMethodMap method has a canonical params/result type and runtime schema; mutation methods require a requestId context, and the protocol contract fixture typechecks every method and five ToolOutput envelopes.
- Every branded value has one accepted wire grammar; IsoDateTime, HostName, FacetPath, and BriefMaterialRef have boundary fixtures; all public object schemas reject unknown keys and share the same WIRE_LIMITS, safe-integer, and JSON-value rules.
- The 35 EngineMethodMap keys have an exact mutation/query partition and JSON-round-trippable results; empty success is always null.
- tools/list returns exactly the ordered five-registry descriptors, including reviewed title/description copy, draft-2020-12 input/output schemas, and readOnly/destructive/idempotent/openWorld hints; get and pending actions cannot return another action's success shape.
- Every MCP call returns the one output-parser-validated ToolOutput in structured content and its exact JSON encoding in text content; invalid, domain, presenter, and unexpected outcomes use typed WireFailure results, with unknown exceptions redacted to non-retryable `internal_error`. The server name/version source, exact ReviewLaunch ref/route, borrowed-client ownership, and bounded idempotent EOF/signal/transport-error teardown have transport-level fixtures.
- already_exists returns one typed subject, ambiguous_subject returns at least two typed candidates, and other errors cannot carry subject resolution or non-JSON details. An unknown EngineEvent is not delivered to a handler and causes a schema_unsupported full reread.
- Two concurrent clients creating the same identity in one space cannot both succeed; People/bootstrap, inline-space resolution, exact duplicate rules, and malformed or reused RequestIds are deterministic.
- MCP, Panel, and CLI can share one local runtime only after production composition exists, while retaining host/user actor identity and a distinct engine-owned lease owner per client; Panel materials and doctor views use typed methods rather than opening persistence.
- Once all core handlers exist, the production runtime is constructible without callers importing internal repositories; earlier slices keep composition internal, and file ingest crosses only a narrow parser port.
- The production CLI's `distill` command holds one process, client session, LeaseOwnerId, and stable commit RequestId across brief, draft editing, renew, and commit. Its current-user-only envelope omits owner authority, detects briefing snapshot changes, and cannot be resumed by a second process that merely knows the lease id.
- At most one suspended target exists per subject; stale-parent promotion and rollback during active review fail with review_conflict; candidate replacement records its separate derivation edge; immutable versions are not rewritten by promote/reject.
- MCP correct has no unreachable current branch, while host/sdk/executor/system correction is mechanically suspended with relayed provenance and user correction retains direct-user provenance.
- Exported bundle claims have locally verifiable excerpt evidence after import; no dangling external MaterialId survives.
- The first usable release cannot be declared before fresh-install setup, both host bindings, the local Panel, security refusal tests, and next-chat Recall pass from built artifacts.
- The first release performs no Distilly account login, Profile Catalog network call, hidden sync, background executor start, or telemetry upload.
## Risks
- V3 is deliberately detailed and can be mistaken for shipped behavior. Every entry document must retain the target-versus-live distinction, and architecture.md must change only with implementation.
- Step 8 can look executable because its tests launch a real stdio child, but that child deliberately injects fake dependencies and is not a usable Distilly runtime. Package exports, binary manifests, architecture claims, setup, and fresh-install gates must preserve that distinction until production composition lands.
- A claim-only deterministic renderer may initially sound less polished than free prose. The mitigation is to improve claim quality and renderer templates while keeping every factual sentence traceable, not to restore a second unconstrained prose truth.
- The 30-minute first lease may be wrong for some hosts. The returned expiry and renew action make the behavior visible; changing the internal default within the same semantic contract does not require a wire break.
- Incremental briefing retains baseline claims instead of every historical material body. A revision that needs an old quote can retain the existing EvidenceRef; a full semantic reconsideration is an explicit, size-bounded redistill. Tests must catch accidental claim loss.
- Auto-v1 has no timer, so a two-item set older than 30 minutes queues only on the next ingest attempt, including a duplicate-only attempt. The product must describe this as attempt-driven batching rather than implying a background executor exists.
- Conservative wire limits can reject an unusually large single material, correction, patch, or briefing even when a particular host could carry it. The product must return the exact failed boundary and remediation; raising a shared limit requires updating both runtime and JSON schema snapshots, while large research sets should be split across ingest calls rather than silently accepted by only one entry point.
- Importing a public bundle as suspended adds friction. Trusting a remote profile directly as local current would be worse because provenance policy, moderation, and publisher identity are outside the local engine's authority.
- Platform plugin rules can change. V3 isolates current distribution facts in the plugin chapter and keeps the engine independent, but release maintainers must re-check official host documentation before publishing.
- Superseding all current authority links is a wide documentation diff. Mechanical link and generated-chapter gates do not prove the prose is still historically accurate, so the final review must distinguish live authority from historical V2 references.
@@ -1,68 +0,0 @@
# Agent Note: bound private UI capture to one foreground consented scope
Status: proposed
## Problem
Distilly can research public figures and creators through public web pages, posts, captions, transcripts, OCR, and user-provided files. A private contact is different. The useful source may be a one-to-one message thread visible only in a signed-in desktop application, and a capable host such as Codex may be able to inspect that application through Computer Use. Treating this as another SourceAdapter or as ordinary vision would erase the important boundaries: whose account and thread may be opened, which range may be read, whether unrelated messages are visible, how the host processes screen frames, and whether screenshots become permanent local facts.
Operating-system Screen Recording or Accessibility permission proves only that the host can operate the computer. A host-level “Always allow” choice likewise does not prove that the user consented to process a particular conversation for profile distillation. Chat text cannot carry a trustworthy `consent=true` flag because the model and on-screen content are both untrusted at the authorization boundary. Group chats, attachments, links, audio, and background scrolling also add participants and side effects that the first implementation cannot reliably isolate.
The target therefore needs a host-owned authorization seam and an engine-owned provenance stamp before private UI material can enter the same ingest, briefing, evidence, and commit path as other text. This is a new privacy and trust decision, separate from the general Design V3 workflow recorded in [the Design V3 Agent Note](2026-08-20-design-v3.md).
## Proposal
Private UI capture is an optional HostBinding capability with a narrower contract than the host platform may technically allow. It is foreground-only, one-shot, user-attended, read-only, and bounded to an exact application and account, an exact one-to-one thread, a canonical existing-or-create subject target, and a visible-message or time range. The purpose is fixed to profile distillation and the first release is text-only. A create target is not persisted during authorization; the subject and first transcript batch remain one atomic ingest.
Before the first screen frame is captured, a trusted native host UI displays the complete scope, discloses that the host will process screen content, states what Distilly will retain, asks the user to attest that they have authority to process the selected content, and obtains an explicit confirmation. The product does not claim that this attestation verifies the other participant's consent or establishes a legal basis. Completion, cancellation, idle timeout, screen lock, account/thread/window change, scope escape, or client close invalidates the grant. Expanding the range requires another grant.
The host binding owns a `PrivateUiCaptureController`. Its trusted UI returns either a typed refusal or a non-serializable, one-shot grant handle whose live status can be checked before every frame and revoked on any boundary change. `LocalRuntime` registers a host-native, user-gesture action and composes the controller, a narrow liveness adapter, and an engine-owned capture session. The coordinator atomically binds the handle once, opens the engine session, drives Computer Use, submits one logical transcript batch through `IngestService` with engine-owned `enqueue="now"`, completes the audit, and returns a typed ingested/refused/aborted/failed result to the current host task. Duplicate-only capture still returns an existing or newly upserted pending job when the full set has undistilled changes. This action is not listed in MCP `tools/list`; the five model-facing schemas gain no grant or audit field, generic `EngineRuntime.connect` carries no capture privilege, and the public SDK cannot construct or replay the trusted context.
The engine alone creates the unpredictable `CaptureAuditRef`, stores the normalized subject target in session memory, computes keyed scope and conversation identifiers, writes content-free start/stop events, and stamps accepted material. The capture session enforces a closed cross-field shape: transcript, conversation, private access, personal communication, `computer_use_transcript`, and private sensitivity. It rejects public/shareable labels, web/article carriers, caller-provided URI/artifact/thread/account fields, a second request, or a target that differs after canonicalization. Every written start has exactly one closed stop: successful ingest completes normally, a controller/guard failure preserves its trusted revoke reason, an engine rejection writes `ingest_rejected`, an active-guard coordinator exception uses parameterless abort to write `coordinator_aborted`, and crash recovery writes `process_terminated`; no caller supplies a reason or count. A successful create records the engine-generated SubjectId atomically; a duplicate or ambiguous create closes the grant and requires fresh authorization for the selected existing target.
The host isolates the target window or region and closes notifications before capture. It fails closed on a wrong account or thread, another-chat sidebar exposure, notification, OTP, payment data, secret, or inability to maintain isolation. Operations are read-only: no send, reply, reaction, delete, forward, download, link opening, or settings change. The UI discloses that scrolling may affect read state. Every on-screen instruction remains untrusted data and cannot widen the scope or alter the tool sequence.
The first implementation rejects group chats and attachments, images, audio, files, and links. It retains only the normalized private transcript, the MaterialRecord provenance, and content-free audit events. Screenshots, recordings, clipboard contents, credentials, thread names, and account labels do not enter RawStore, logs, telemetry, or diagnostic bundles. The audit keeps only an installation-keyed scope digest, host, versioned data-policy and retention disclosures, start and stop times, a closed stop reason, engine-derived accepted counts, and trusted guard boundary counts. The caller cannot report its own audit totals or free-form outcome. Distilly's local-first claim applies to its own storage; it does not claim that a host never processes or retains screen frames under the host's data controls. If the binding cannot disclose that policy, cannot gate each frame in a scoped window/region, or cannot return the action result to the current task, the capability is unsupported.
Source diversity uses a separate installation-keyed `ConversationSourceKey`, never the grant id or a bare hash of low-entropy labels. A controller-provided stable opaque application/account/thread locator keeps the same conversation grouped across grants and label changes; if the binding cannot provide one, the engine conservatively groups all private captures for that SubjectId. Private conversation groups are never diversity-eligible. The HMAC key is installation-scoped across MCP, Panel, CLI, and restarts, is never exported or logged, and cannot be silently regenerated if lost because doing so would inflate one conversation into several apparent sources.
The grant cannot be used by a lifecycle hook, scheduled task, subagent, background or locked-mode run, or DistillExecutor. An executor may distill materials already present in the fact layer but may not reopen the private application. Revoking a grant stops future capture; withdrawing or privacy-purging the stored materials is a separate fact-layer action.
This boundary follows the platform guidance that Computer Use on macOS requires Screen Recording and Accessibility permissions, may process screen, screenshot, window, keyboard, and clipboard data, should be scoped to the needed application/window, and exposes approval choices including “Always allow” ([OpenAI Computer Use documentation](https://learn.chatgpt.com/docs/computer-use)). Distilly deliberately adopts the stricter per-conversation rule instead of treating the platform's maximum capability as the product default.
## Alternatives considered
- **Treat operating-system permission or “Always allow” as conversation consent** — rejected because capability permission does not identify the subject, thread, range, purpose, or retention policy and can outlive the user's immediate intent.
- **Let the model submit `consent=true`, a CaptureAuditRef, public/shareable access, or an `independent` source flag in MaterialInput** — rejected because chat and screen content are untrusted and would be able to forge the very provenance the audit is meant to prove.
- **Make private messaging a SourceAdapter** — rejected because a provider adapter suggests reusable background collection and vendor credentials, while this lane is an attended host UI operation with per-run consent and screen-leak risk.
- **Expose capture as a sixth model-facing MCP tool** — rejected because a model tool cannot itself prove the native user gesture, first-frame gate, non-replayable grant, or live window/thread boundary. The host-native action returns to the same canonical workflow without weakening the five-tool contract.
- **Allow rolling, background, locked-screen, lifecycle-hook, subagent, or executor capture** — rejected even if a host platform supports those modes. The user cannot continuously verify the active account, thread, notifications, and scope.
- **Capture the full screen or retain screenshots/raw recordings by default** — rejected because unrelated chats, notifications, credentials, and third-party data would become durable facts without improving the normalized text evidence path.
- **Support group chats and attachments in the first implementation** — rejected because participant separation, author attribution, downloading, malicious files, and raw-media retention each require an additional consent and provenance design.
- **Treat the user's authority attestation as verified participant consent** — rejected because Distilly cannot establish the other participant's consent or make a legal determination from a checkbox.
- **Forbid all private-contact material** — rejected because user-provided or precisely consented one-to-one text is a high-value source and can use the same evidence-bounded profile pipeline once capture is isolated from collection automation.
## Acceptance criteria
- A synthetic host fixture cannot capture its first frame until trusted UI confirms the exact app/account, one-to-one thread, canonical existing-or-create subject target, range, text-only purpose, host-processing disclosure, Distilly retention, and user authority attestation.
- Screen Recording, Accessibility, or a prior “Always allow” choice never bypasses the per-scope confirmation.
- The grant expires on completion, cancellation, timeout, lock, account/thread/window change, boundary escape, or client close; a larger range requires another grant. Atomic bind prevents replay, and revocation after authorization but before ingest is rechecked by the engine.
- Wrong-account/window, another-chat sidebar, notification, OTP, payment, secret, group-chat, attachment, link, background, locked, scheduled, subrun, hook, and executor fixtures fail closed before material is accepted.
- Capture performs no send, reply, reaction, delete, forward, download, link opening, or settings action, and its UI discloses possible read-state changes from scrolling.
- A normal MCP or SDK caller cannot construct PrivateUiCaptureContext or CaptureAuditRef. A transcript without the matching trusted session or canonical subject target is rejected, as are forged public/shareable/web/article/URI/artifact fields; a refused create leaves no empty subject.
- A successful synthetic capture stores only normalized private transcript, provenance, and content-free audit. An aborted capture stores no screenshot, recording, clipboard, credential, account label, thread label, or transcript fragment in RawStore, logs, telemetry, or diagnostics.
- Audit records include keyed scope digest, host, disclosure versions, start/stop time, closed stop reason, engine/guard-derived counts, and boundary refusals, and contain no message body or caller-supplied totals.
- Successful, rejected-ingest, coordinator-abort, and process-recovery fixtures each leave exactly one content-free stop for every written start.
- The same stable opaque conversation locator produces the same source key across label changes, different locators with identical display labels do not collide, and the subject fallback cannot inflate private-source diversity. Two runtimes and a process restart use the same installation key.
- The host-native capture action returns its typed refusal, abort, engine failure (including subject resolution), or `IngestResult` to the task that invoked it while MCP `tools/list` remains exactly five; changed or still-undistilled input reaches a pending job because enqueue is fixed to `now`.
- Revocation prevents subsequent capture while withdrawal/privacy purge removes already stored transcript facts according to the existing deletion contract.
- A binding with unknown screen-data policy or no window/region isolation reports the lane as unsupported and offers paste/export text as the fallback.
- Tests use synthetic windows and messages only; no live personal account or conversation is required.
## Risks
- Some hosts may not expose a trusted native consent result, non-replayable grant, per-frame revocation signal, stable conversation locator, current-task action result, or window-scoped capture primitive. Missing locator alone uses the conservative subject fallback; missing any authorization, liveness, isolation, or result-return primitive makes the lane unavailable rather than permitting a prompt-only approximation.
- Window isolation can still fail because desktop applications change layout or show overlays. Fail-closed conformance fixtures and a narrow first-release scope reduce but do not eliminate visual ambiguity.
- Host data retention is outside Distilly's fact store. The product must keep the disclosure current and avoid saying “fully local” when screen frames are processed by a host service.
- Content-free audit reduces privacy risk but also limits forensic detail. Keyed scope identity, disclosure versions, derived counts, host, and stop reason are intentionally the maximum retained operational evidence.
- A private transcript can still be sensitive after screenshots are discarded. Private-by-default sensitivity, evidence inspection, withdrawal, and privacy purge remain necessary.
@@ -1,43 +0,0 @@
# Agent Note: Developer Preview source-adapter boundary
Status: proposed
## Problem
The V3 contract kept SourceAdapter as an extension seam but prohibited first-party provider APIs in the initial repository. That would leave the Developer Preview unable to preserve useful source collection without either treating the Plugin as a thin skill wrapper, giving credentials to the host model, adding model-facing collection tools, or reviving browser automation for private chats.
## Proposal
The Developer Preview will support exactly Codex and Claude Code and keep the model-facing surface at exactly five MCP tools. The product may provide reviewed TypeScript builtins in `@distilly/adapters`: region-explicit Lark/Feishu collection, DingTalk documents and knowledge bases with message history returning non-retryable `host_unsupported`, scope-bound and provider-rate-aware Slack messages, bounded and directly confirmed Xquik public-post candidates, and deterministic local parsers for common text, export, mail, subtitle, and embedded-text PDF formats.
Credentialed collection will be a direct user action in CLI or Panel. Configuration will store only opaque secret references; production composition will resolve values at the adapter boundary and never expose them to the model, browser state, EngineClient, material, briefing, logs, or diagnostics. Panel collection will use a separate strict `/sources` transport over an injected user-bound UserCollectionClient; it will not weaken the exact EngineMethodMap `/rpc` contract. Xquik will receive an injected, non-persisted MeteredReadConsentPort rather than a config value or serialized grant. Adapter output will reach authority only through a user-bound EngineClient ingest call, so collection adds no Protocol method, CoreEngineClient method, or MCP descriptor.
SourceAdapter will remain generic over an adapter-owned resource schema so community adapters are not closed out by the builtin selection union. DingTalk `messages` will remain a known builtin resource whose resource-bound preflight and collect path fail before secret resolution or network I/O. One raw input will still produce at most one canonical text. Mailbox and Lark exports will use read-only subject display names, aliases, and identity hints to filter exact target records before deterministic aggregation; missing or ambiguous target identity will leave the raw unparsed instead of attaching a multi-person export to one subject. Output will use the existing inclusive one-material content limit and will never be silently truncated.
Developer Preview bindings will report private UI capture unavailable and ship no browser, Playwright, Computer Use, screenshot, or recording path for private messages. Users will provide exports or pasted text, or explicitly run a reviewed official-API adapter within its granted scope. The more extensive private-capture types remain a future contract and are not an installable Preview capability.
## Alternatives considered
- Keeping all provider collection outside the repository was rejected because the Plugin would preserve orchestration but omit the repeatable source acquisition that users rely on.
- Adding `distilly_collect` or adapter-specific MCP tools was rejected because it would break the exact-five contract and let a model reach credentialed, potentially billable operations.
- Passing tokens in model prompts, config values, CLI flags, or Panel browser payloads was rejected because those surfaces are routinely persisted or inspected.
- Browser automation for private chats was rejected because the Preview cannot prove account/thread isolation, screen-data handling, or zero incidental capture in Codex and Claude Code.
- Advertising DingTalk message history through an unofficial fallback was rejected because the reviewed Preview integration only has a supportable documents/knowledge-base contract; capability absence is `host_unsupported`, not malformed user input.
- Closing the generic adapter request type around four builtins was rejected because it would contradict the community SourceAdapter seam; each adapter instead owns a strict resource schema.
- Reusing Panel `/rpc` for collection was rejected because `/rpc` is the exact EngineMethodMap transport. A separate injected `/sources` client keeps collection out of Protocol and the engine method registry.
- Auto-detecting Lark region, allowing unbounded Xquik queries, persisting Xquik consent, or assuming Slack's historical page size were rejected because region, billing consent, and rate limits must be explicit and current.
- Splitting one export into an unbounded material array was rejected because the current raw-parser invariant is one canonical text; exact target filtering plus bounded deterministic aggregation preserves that invariant without misattributing a multi-person export.
## Acceptance criteria
- Codex and Claude Code are the only Preview host manifests, and both report private UI capture unavailable.
- Exactly five MCP tools remain; source list/configure/preflight/collect exist only on direct user CLI and Panel paths.
- Panel injects a UserCollectionClient, exposes a strict authenticated `/sources` route, and binds source mutations to route-aware one-use nonces without accepting secret values in browser payloads.
- Every registered adapter supplies a strict resource schema. Builtin and community resources validate before secret resolution or network I/O.
- Lark region routing, DingTalk message refusal, Slack scope/rate handling, and Xquik bounded consent are covered by offline fixtures with secret-redaction assertions.
- Local parsers cover the listed real formats, exact subject filtering, an inclusive 1,048,576-byte success boundary, and a 1,048,577-byte unparsed failure without truncation.
- The final implementation updates live architecture and tests before this Note moves to `implemented`.
## Risks
The source action surface adds a fourth authenticated Panel POST route and a second borrowed client, so route isolation, nonce cross-replay, teardown ownership, and response bounds need dedicated tests. Dynamic community resource schemas could become a validation bypass if the registry exposes an unparsed adapter handle. Provider API scope and pagination behavior can change, so production code must fail closed against injected offline contracts and never infer a broader permission. This Note describes target behavior only; adapters, runtime, CLI, Panel controls, and full bindings are not shipped yet.
@@ -1,63 +0,0 @@
# Agent Note: full-lifecycle governance for the TypeScript tree
Status: proposed
## Problem
The original governance layer on this branch — documentation contracts, the Agent Note tree, a governed-diff gate, per-directory instructions, and a pre-push hook — was implemented in Python and inspected only Python and Markdown. TypeScript product code introduces failures that layer cannot see by itself: a package that type-checks but publishes broken export maps, an accidental public export, an upward dependency or cycle, an unhandled Promise, a non-exhaustive `switch`, or a model-visible projection that changed without anyone reading the diff.
There is also a sequencing trap. If TypeScript gates arrive only after the product code, the first several product PRs land with no mechanical checks at all, and the branch learns to treat red as normal.
## Proposal
Land the gate suite with the TypeScript foundation, before later product slices, and define the whole lifecycle rather than a pile of commands. V2 first specified the lifecycle; [V3 §27](../../../../docs/design/v3/27-testing-and-governance.md) is now the in-force contract for the evidence classes and gates. This note owns why the shape is that way and what was rejected.
Sixteen stages from bootstrap to monthly audit, each with an owner and a verdict. The ones that carry the design weight:
- **`pnpm run gates` is the single aggregator.** It runs the fast checks, typecheck, tests, model-visible snapshots, documentation, notes, build, and hygiene in that order, printing each exact command so a report can quote it instead of paraphrasing. Existing practice already forbids writing only "tests pass".
- **Fast and full are separate entry points.** `gates:fast` is what a task agent runs against the surface it changed; `gates` is the CI equivalent. Defaulting to the full suite locally teaches agents to skip checks.
- **The release face has its own gates.** `publint` and a type-resolution check plus an import smoke over the built entry, because the common TypeScript failure is a package that passes every source test and still cannot be imported.
- **`knip` enforces unused and undeclared dependency hygiene.** The built self-import also asserts the foundation's exact runtime root exports. A dedicated import-boundary and cycle gate lands with the second package, when §25 has a real cross-package edge to inspect; Knip alone cannot prove that an otherwise declared and used import points in the allowed direction.
- **Coverage is reported, not gated per file.** Per-file totals are a resource commitment this tree cannot honor yet, and a covered line is not an assertion.
- **Model-visible output is snapshot-tested without any key.** `prompt()`, `SKILL.md`, and host instructions are the product's real output; a changed snapshot must be explained in the PR, never bulk-accepted.
- **Repository governance has one implementation per check.** V3 does not reserve an empty `@distilly/governance` package. Existing Python doc/Note verifiers remain until a real TypeScript replacement lands with accepted/rejected parity, at which point the Python implementation is deleted in the same PR. The legacy product CI job exists only while `tools/` does and is deleted rather than disabled.
- **The local hook stays cheap.** It runs the deterministic subset and verifies the outgoing local `HEAD`; the remote CI is the only merge authority, because Git cannot force a contributor to install a hook.
- **Gates never score prose.** V3 §27.9 draws the line explicitly: structure, links, exit codes, and export maps are mechanical; whether a rationale is true, whether an assertion would catch the intended defect, and whether every non-trivial change wrote a Note remain semantic review under [code-review.md](../../../../docs/process/code-review.md).
Growth is signal-driven. V3 §27 keeps target gates tied to a behavior or release face, so the suite does not acquire twenty verifiers on day one. A checker with no corresponding failure is pure cost: it slows every change and gets whitelisted after its first false positive.
### Current implementation evidence
The implemented local slice is a pinned `pnpm` workspace, the V3 §29.1 Protocol, Engine through its package-internal Step 7 claim-commit slice, Step 8 injected-client Facade/MCP adapter, and Step 9 capability-only bindings plus canonical plugin source. Its real stages cover formatting and lint, no-emit typecheck across all five packages, fail-closed Vitest discovery, reported coverage, reviewed Protocol/prompt/public-export checks, TypeScript build, package self-import, exports and type resolution, and unused or undeclared dependency hygiene. Protocol gates cover the 35-method schema registry, exact five MCP descriptors, host preflight union, loopback ReviewLaunch relation, wire failures, explicit exports, and the built entry. Engine fixtures exercise real facts, locks, ingest, globally idempotent requests, source grouping, briefing, lease, deterministic claims/quality/rendering, immutable versions, current-or-suspended commit, and target-first recovery while its root remains empty. Facade and MCP fixtures cover every injected mapping plus real stdio conformance over a full test fake. Bindings fixtures cover both host translators, trusted net-capacity evidence, exact release tuple matching, fail-closed provider input, private-capture refusal, registry ordering, explicit exports, and the built package face without host probing or installation. The plugin assembly gate recursively hashes raw canonical skill bytes, exact-mirrors and stale-prunes both host copies, validates the source-only templates and platform manifests, propagates the MCP SemVer, and recomputes canonical release bytes in a clean temporary root. The fail-closed boundary gate now adds exactly `@distilly/bindings → @distilly/protocol` and rejects all reverse/mutual edges through bare, subpath, workspace, link, file, and directory aliases. The CI definition exercises Node 22.19 and 24 across Linux and macOS; Python documentation, Agent Note, plugin assembly, compile, unittest, and Ruff lanes remain. The governed-diff predicate now includes `plugins/` as well as packages and root tool configuration, while preserving the tests-only exemption.
This evidence proves the workspace, serializable Protocol contract, exact current package boundary, Engine fact primitives and package-internal Step 7 behavior, injected-client Facade/MCP projections, stdio transport over a full fake, capability-only host preflight, and canonical source plugin release bytes, not the full lifecycle proposed here. Production runtime/MCP/CLI/setup composition, a `DISTILLY_ROOT` backend, full bindings/injectors, Panel and review/correction/evolution services, public `prompt()`, installable plugins, host workflows, fresh install, release publication, and eventual parity-backed retirement of Python governance remain unimplemented, so this Note remains proposed.
## Alternatives considered
- **Keep the Python gates and add a second TypeScript suite** — rejected: two implementations of the same documentation and Note contracts drift, and the drift is invisible until they disagree about a real PR.
- **Port the gates only after the product code lands** — rejected: the first product PRs would have no mechanical floor, and a branch that starts red stays red.
- **One `gates` command with no fast subset** — rejected: a slow default is the reason agents bypass checks, and the repository already requires choosing the narrowest check the diff can break.
- **Per-file 100% coverage from the start** — rejected: that gate is affordable only with the maintenance budget behind it, and it rewards covering lines over asserting behavior.
- **`oxlint` instead of ESLint** — rejected for now: the rules that matter here are type-aware and JSDoc-shaped. Speed is not the binding constraint on a repository this size.
- **A bundler-produced dist plus source-only tests** — rejected: it hides exactly the publish-face failures stage 10 exists to catch. `tsc -b` emits, and the built entry is smoke-tested.
- **Enable changesets and a release pipeline immediately** — rejected: nothing is published, and documenting a release flow that has never run violates the rule that standard commands must be real.
- **Let the hook run the full suite** — rejected: contributors disable slow hooks, and CI is the authority anyway.
## Acceptance criteria
- `pnpm run gates` exits non-zero for each of these seeded defects, one at a time: a type error, a lint violation, a failing test, an empty test discovery, a broken local Markdown link, a design chapter that drifts from its parent, a governed path changed without a Note, an unused export, and a package whose export map cannot be resolved.
- `pnpm run gates:fast` finishes fast enough to run on every change, and its command list is a strict subset of `gates`.
- Every command written into root `AGENTS.md`, [docs/development.md](../../../../docs/development.md), and [docs/testing.md](../../../../docs/testing.md) has been executed in this repository, or is labeled with the environment and credential it needs.
- CI reports governance once, and reports tests across Node 22 and 24 on Linux and macOS.
- The pre-push hook verifies the local outgoing `HEAD`, not a stale remote head.
- Deleting the last Python product file removes the `legacy-python` job and its documentation in the same change.
- A cold-start agent reads root `AGENTS.md`, the documentation map, and design V3, then names what it may change, where each fact lives, which command to run, and whether a Note is required.
## Risks
- The stage table is longer than the current tree needs. The mitigation is that unimplemented commands stay labeled as design targets, and V3 §27 requires real entry-path evidence before a gate is claimed as available.
- A dual-language window means one PR can pass the TypeScript gates and break the Python ones. CI runs both, so the failure is visible; the risk is agents reading only the job they expected to matter.
- `pnpm run gates` becomes a place to hide slow checks. Keeping the fast subset a strict subset, and printing each command, is what keeps that honest.
- Snapshot tests invite bulk acceptance. Review treats an unexplained snapshot update as a blocker.
- Handing the documentation and Note verifiers from Python to TypeScript is a behavior-preserving rewrite of gates that currently protect the branch. It needs the accepted and rejected cases ported first, so the gate cannot silently become permissive.
- Node's built-in test tooling and `node:sqlite` are still moving. Vitest and the disposable `.index/` keep both changes confined.
@@ -1,34 +0,0 @@
---
name: distilly-code-review
description: Use when reviewing a pull request or an outgoing product diff on the distilly branch.
---
# Reviewing a distilly PR
**This skill is guidance, not a complete checklist.** The contract is [docs/process/code-review.md](../../../docs/process/code-review.md). Query an explicit PR number or URL and require `state=OPEN` for a merge-blocking review; do not let bare `gh pr view` select a merged PR. Read the URL, `baseRefName`, `baseRefOid`, and exact remote `headRefOid` from metadata. Choose the remote matching the PR URL's base repository, then fetch both the base and that remote's `pull/<number>/head`; this covers fork PRs even when `origin` is the contributor fork. Verify the fetched objects equal both metadata OIDs, then diff those immutable SHAs before reading the owning [design chapter](../../../docs/design/README.md). Distilly product PRs normally target `distilly`; do not silently substitute `dot-skill`. Re-establish the range after a retarget or push. A short review with one substantiated blocker is better than a list of nits.
```sh
gh pr view <pr-number-or-url> --json number,state,baseRefName,baseRefOid,headRefName,headRefOid,headRepository,headRepositoryOwner,url
git fetch <base-repository-remote> <baseRefName> pull/<number>/head:refs/remotes/<base-repository-remote>/pr/<number>
git rev-parse <base-repository-remote>/<baseRefName> refs/remotes/<base-repository-remote>/pr/<number>
```
## Walk
1. Confirm the base repository/remote, base ref plus metadata/fetched SHA, and metadata/fetched head SHA. Read the PR body for the design owner, Agent Note, verification, unverified work, risks, and handoff.
2. Open [docs/process/code-review.md](../../../docs/process/code-review.md) and apply every blocking requirement, including the distilly-specific list.
3. Read the chapter that owns the change in [docs/design/v3/](../../../docs/design/README.md). Do not review product behavior against `architecture.md` alone, and do not accept a V2/V1 chapter as the requirement.
4. For async, credentials, adapters, or queues, apply [defensive-patterns.md](../../../docs/process/defensive-patterns.md).
5. For tests, apply [docs/testing.md](../../../docs/testing.md). Confirm the author ran the [narrowest checks](../../../docs/development.md).
6. Semantically review every new prose passage. Format gates do not do this.
7. Report with defect, path, impact, and evidence. Separate blockers from suggestions. Omit issues a green gate already caught.
## Red flags — stop and re-read the design
- "The compressed architecture is enough"
- "I'll note the work.md split as a follow-up"
- "ingest and commit can be one helper for now"
- "A style nit list with no blocker"
- "CI is green so the prose is fine"
Those mean the review has not happened yet.
@@ -1,15 +0,0 @@
---
name: distilly-doc-standards
description: Use when writing, moving, or reviewing documentation on the distilly branch — choosing which file owns a fact, generating design chapters, adding Agent Notes, or responding to verify_docs failures.
---
# distilly documentation workflow
Contracts live in [docs/AGENTS.md](../../../docs/AGENTS.md). This skill is the walk, not a second standard.
1. Name the document's subject and its direct children. Keep full detail only for the subject.
2. Choose the folder: product contract → `docs/design/`; live tree → `architecture.md`; steps → `cookbook/`; review rules → `docs/process/`; why → Agent Note.
3. Do not compress [system-v3.md](../../../docs/design/system-v3.md) into architecture.md. Edit only the in-force parent design file, then run `python3 scripts/sync_design_chapters.py`; generated chapters are not hand-edited, and deprecated V2/V1 bodies remain historical.
4. Search `.agents/notes/` before adding a note. Update the owner or cross-link.
5. Run `python3 -B scripts/verify_docs.py` after Markdown edits. Run `python3 -B scripts/verify_agent_notes.py` when Notes changed.
6. Standing docs stay present tense. The design corpus may keep conversation density.
@@ -1,35 +0,0 @@
---
name: distilly-pre-push-checks
description: Use before pushing or opening a PR on the distilly branch to run the smallest checks that can fail for the outgoing diff.
---
# distilly pre-push checks
This workflow verifies commits that are about to leave the clone. The head is therefore the current local `HEAD`, including commits that PR metadata cannot see yet. Use PR metadata only to establish the base branch and confirm that the checked-out branch is the PR head branch. The checkout must be clean so tree gates read the outgoing commit; preserve unrelated work and use a separate clean worktree instead of deleting, stashing, or bundling it without the owner's direction.
```sh
git status --short --branch
git rev-parse HEAD
gh pr view <pr-number-or-url> --json number,state,baseRefName,baseRefOid,headRefName,headRefOid,headRepository,headRepositoryOwner,url
git remote -v
git fetch <base-repository-remote> <verified-base>
git rev-parse <base-repository-remote>/<verified-base>
git diff --stat <baseRefOid>...HEAD
```
Always pass an explicit PR number or URL. Reject metadata unless `state` is `OPEN`, `headRefName` is the checked-out branch, and the head repository matches the push destination; a bare `gh pr view` may silently return an old merged PR. Choose the remote whose URL matches the base repository in the PR URL; in a fork clone this is normally `upstream`, not `origin`. Verify the fetched base resolves to metadata `baseRefOid`, then store that exact SHA so the hook can reproduce the range: `git config branch.<branch>.distillyBase <baseRefOid>`. Without a matching open PR, require an explicit base-repository remote ref or exact SHA. Only a direct push to the persistent `distilly`, `dot-skill`, or `main` branch uses its existing remote tip as the base.
Then run only what the diff can break:
| If the diff touches | Run |
|---|---|
| Markdown | `python3 -B scripts/verify_docs.py` |
| `.agents/notes/` | `python3 -B scripts/verify_agent_notes.py` |
| Governed outgoing diff | `python3 -B scripts/verify_agent_notes.py --base <resolved-base-sha> --head <local-head-sha>` |
| `tools/` or `scripts/` | compile, Ruff, and the owning unittest file from `docs/development.md` |
| Tests only | that test file |
| Product behavior or a PR to merge | [distilly-code-review](../distilly-code-review/SKILL.md) |
Do not run the full unittest suite by default. CI on `distilly` / `dot-skill` owns the matrix.
If this clone enables `.githooks`, it reads the outgoing ref records from Git, resolves the same configured base, and checks the clean checked-out head. It rejects another local branch in the same push, persistent-branch deletion, and non-fast-forward persistent updates. Do not bypass it unless the user explicitly asks. Before transfer or PR, produce the handoff from `docs/development.md`.
-112
View File
@@ -1,112 +0,0 @@
#!/bin/sh
set -eu
repo_root=$(git rev-parse --show-toplevel)
cd "$repo_root"
status_output=$(git status --porcelain --untracked-files=normal) || {
echo "pre-push: cannot inspect the working tree" >&2
exit 1
}
if [ -n "$status_output" ]; then
echo "pre-push: the checked-out working tree must be clean" >&2
echo "preserve unrelated work; push from a separate clean worktree if needed" >&2
exit 1
fi
current_ref=$(git symbolic-ref -q HEAD) || {
echo "pre-push: detached HEAD is not supported; check out the branch being pushed" >&2
exit 1
}
current_oid=$(git rev-parse HEAD)
python3 -B scripts/verify_docs.py
python3 -B scripts/verify_agent_notes.py
ruff check tools scripts tests
is_zero_oid() {
case "$1" in
""|*[!0]*) return 1 ;;
*) return 0 ;;
esac
}
while read -r local_ref local_oid remote_ref remote_oid; do
if is_zero_oid "$local_oid"; then
case "$remote_ref" in
refs/heads/distilly|refs/heads/dot-skill|refs/heads/main)
echo "pre-push: refusing to delete persistent branch $remote_ref" >&2
exit 1
;;
refs/heads/*|refs/tags/*)
continue
;;
*)
echo "pre-push: unsupported remote ref $remote_ref" >&2
exit 1
;;
esac
fi
case "$remote_ref" in
refs/tags/*)
continue
;;
refs/heads/*)
if [ "$local_ref" != "$current_ref" ] || [ "$local_oid" != "$current_oid" ]; then
echo "pre-push: branch gates only cover the clean checked-out HEAD" >&2
echo "check out $local_ref at $local_oid and push it separately" >&2
exit 1
fi
if [ "$local_ref" != "$remote_ref" ]; then
echo "pre-push: local and remote branch names must match" >&2
exit 1
fi
;;
*)
echo "pre-push: unsupported remote ref $remote_ref" >&2
exit 1
;;
esac
case "$remote_ref" in
refs/heads/distilly|refs/heads/dot-skill|refs/heads/main)
if is_zero_oid "$remote_oid"; then
echo "pre-push: cannot verify creation of persistent branch $remote_ref" >&2
exit 1
fi
if ! git merge-base --is-ancestor "$remote_oid" "$local_oid"; then
echo "pre-push: refusing a non-fast-forward update to $remote_ref" >&2
exit 1
fi
base_ref=$remote_oid
range_mode=direct
;;
refs/heads/*)
branch_name=${local_ref#refs/heads/}
base_ref=${DISTILLY_BASE_REF:-}
if [ -z "$base_ref" ] && [ "$branch_name" != "$local_ref" ]; then
base_ref=$(git config --get "branch.$branch_name.distillyBase" || true)
fi
if [ -z "$base_ref" ]; then
echo "pre-push: no verified base for $local_ref" >&2
echo "set DISTILLY_BASE_REF=<remote-ref-or-sha>, or run:" >&2
echo " git config branch.$branch_name.distillyBase <resolved-base-sha>" >&2
exit 1
fi
range_mode=merge-base
;;
esac
base_oid=$(git rev-parse --verify "${base_ref}^{commit}") || {
echo "pre-push: base does not resolve to a commit: $base_ref" >&2
exit 1
}
git cat-file -e "${local_oid}^{commit}" || {
echo "pre-push: local head does not resolve to a commit: $local_oid" >&2
exit 1
}
python3 -B scripts/verify_agent_notes.py --base "$base_oid" --head "$local_oid" --range-mode "$range_mode"
if [ "$range_mode" = direct ]; then
git diff --check "$base_oid..$local_oid"
else
git diff --check "$base_oid...$local_oid"
fi
done
+4 -4
View File
@@ -29,10 +29,10 @@ assignees: []
## Environment / 环境
- OS: <!-- e.g. macOS 14.5, Ubuntu 22.04 -->
- Python: <!-- output of `python3 --version` -->
- Host: <!-- Claude Code / OpenClaw / other -->
- colleague-skill commit: <!-- output of `git rev-parse --short HEAD` -->
- Data source: <!-- Feishu / DingTalk / Slack / Confluence / WeChat / manual -->
- Node.js: <!-- output of `node --version` -->
- Host and version: <!-- Codex / Claude Code / future host -->
- Distilly commit: <!-- output of `git rev-parse --short HEAD` -->
- Input type: <!-- TXT / Markdown / JSON / SRT/VTT / pasted text / public URL -->
## Additional context / 补充信息
-3
View File
@@ -3,6 +3,3 @@ contact_links:
- name: 💬 Discord community
url: https://discord.gg/NVX66RxWZv
about: Chat, ask quick questions, and share skills. / 聊天、快速提问、分享 skill。
- name: 🖼️ Skill gallery
url: https://titanwings.github.io/colleague-skill-site/
about: Browse or submit a community skill. / 浏览或提交社区 skill。
+5 -5
View File
@@ -1,6 +1,6 @@
---
name: Question / 提问
about: Ask how to use colleague.skill. / 咨询如何使用 colleague.skill。
about: Ask how to use the Distilly Developer Preview. / 咨询如何使用 Distilly Developer Preview。
title: "[Question] "
labels: ["question"]
assignees: []
@@ -10,7 +10,7 @@ assignees: []
Before opening a question, please check:
提问之前请先看:
- README.md / INSTALL.md / SKILL.md
- README.md / INSTALL.md
- Existing issues and discussions / 已有的 issue 和讨论
- Discord: https://discord.gg/NVX66RxWZv
-->
@@ -22,6 +22,6 @@ Before opening a question, please check:
## Environment / 环境
- OS:
- Python:
- Host: Claude Code / OpenClaw / other:
- Data source:
- Node.js:
- Host and version:
- Input type:
+3 -3
View File
@@ -9,7 +9,7 @@
- Base repository/remote, ref, and resolved metadata SHA:
- Remote review head SHA (must match current PR metadata):
- Owning design / issue:
- Agent Note, or why this diff is outside governed paths:
- Rejected alternative and why:
## Changes / 变更
@@ -41,11 +41,11 @@
- Workspace state and pre-existing changes to preserve:
- External state: branch, CI, reviews, required decision:
For a completed PR, the fields in this template are the handoff. Use the full contract in [docs/development.md](../docs/development.md#agent-handoff) when work is incomplete or transferred.
For a completed PR, the fields in this template are the handoff. For incomplete work, state the exact next action and leave the branch reproducible.
## Checklist / 检查清单
- [ ] I read the applicable root and local `AGENTS.md` files
- [ ] I read the current architecture and owning V3 design chapter
- [ ] Current behavior and target design are not presented as the same thing
- [ ] Docs changed with behavior, and generated chapters were not hand-edited
- [ ] Verification and unverified items above are complete and truthful
+11 -22
View File
@@ -2,16 +2,16 @@ name: CI
on:
push:
branches: [dot-skill, distilly, main]
branches: [dot-skill, distilly, distilly-plugin, main]
pull_request:
branches: [dot-skill, distilly, main]
branches: [dot-skill, distilly, distilly-plugin, main]
permissions:
contents: read
jobs:
governance:
name: Governance
repository:
name: Repository checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -22,15 +22,14 @@ jobs:
python-version: "3.11"
- name: Documentation contracts
run: python -B scripts/verify_docs.py
- name: Agent Note tree, governed diff, and whitespace (PR)
- name: Whitespace (PR)
if: github.event_name == 'pull_request'
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
python -B scripts/verify_agent_notes.py --base "$BASE_SHA" --head "$HEAD_SHA" --range-mode merge-base
git diff --check "$BASE_SHA...$HEAD_SHA"
- name: Agent Note tree, governed diff, and whitespace (push)
- name: Whitespace (push)
if: github.event_name == 'push'
env:
BEFORE_SHA: ${{ github.event.before }}
@@ -38,11 +37,9 @@ jobs:
run: |
case "$BEFORE_SHA" in
"") echo "Push event has no before SHA" >&2; exit 1 ;;
*[!0]*) ;;
*) echo "Cannot verify a push with an all-zero before SHA" >&2; exit 1 ;;
*[!0]*) git diff --check "$BEFORE_SHA..$HEAD_SHA" ;;
*) git diff-tree --check --root "$HEAD_SHA" ;;
esac
python -B scripts/verify_agent_notes.py --base "$BEFORE_SHA" --head "$HEAD_SHA" --range-mode direct
git diff --check "$BEFORE_SHA..$HEAD_SHA"
typescript:
name: TypeScript Node ${{ matrix.node-version }} / ${{ matrix.os }}
@@ -97,7 +94,7 @@ jobs:
run: pnpm run hygiene
test:
name: Python ${{ matrix.python-version }}
name: Repository Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -111,17 +108,9 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Compile all Python sources
run: |
python -m compileall -q tools scripts tests
if [ -d src ]; then python -m compileall -q src; fi
run: python -m compileall -q scripts tests
- name: Run unit tests
run: python -B scripts/run_tests.py
@@ -138,4 +127,4 @@ jobs:
- name: Install lint dependencies
run: pip install -r requirements-dev.txt
- name: Run Ruff
run: ruff check tools scripts tests
run: ruff check scripts tests
+44 -28
View File
@@ -1,41 +1,57 @@
# Generated dot-skill outputs (user data, not committed)
skills/*
!skills/colleague/
skills/colleague/*
!skills/colleague/example_zhangsan/
!skills/colleague/example_zhangsan/**
!skills/colleague/example_tianyi/
!skills/colleague/example_tianyi/**
!skills/colleague/example_jiaxiu/
!skills/colleague/example_jiaxiu/**
# Personal agent/developer instructions (kept local, never published)
.agents/
.githooks/
AGENTS.md
CLAUDE.md
/.codex/
/.claude/
# Python
__pycache__/
*.py[cod]
*.pyo
.venv/
venv/
*.egg-info/
# Local configuration, credentials, and product data
.env
.env.*
!.env.example
/adapters.toml
/distilly.toml
/.colleague-skill/
/.distilly/
/knowledge/
/skills/
*.db
*.db-*
*.sqlite
*.sqlite-*
*.sqlite3
*.sqlite3-*
# TypeScript workspace
# Old verification/community files that do not belong to the Plugin branch
/SKILL.md
/openarena-claim.txt
/docs/assets/wechat-group-qr-*.png
# TypeScript workspace output
node_modules/
packages/*/lib/
coverage/
artifacts/
*.tsbuildinfo
# Config with credentials
~/.colleague-skill/
# Python tooling output
__pycache__/
*.py[cod]
*.pyo
.coverage
.pytest_cache/
.ruff_cache/
.venv/
venv/
*.egg-info/
# Playwright browser data
# Browser and test output
playwright-data/
playwright-report/
test-results/
*.log
# Temp files
/tmp/feishu_*.txt
/tmp/email_*.txt
/tmp/dingtalk_*.txt
knowledge/
# OS
# OS files
.DS_Store
Thumbs.db
+1 -3
View File
@@ -7,11 +7,9 @@ lib/
node_modules/
coverage/
# Python legacy tree (see design/v3/28-migration-and-compatibility.md).
/prompts/
# Python repository tools.
/scripts/
/tests/
/tools/
# Plugin release bytes are generated and verified by assemble_plugins.py.
/plugins/release-manifest.json
-74
View File
@@ -1,74 +0,0 @@
# AGENTS.md
Route work by the checked-out branch before editing: `distilly` is the target product path; `dot-skill` is maintenance of the currently published skill; another branch inherits its verified PR base. Never infer branch identity from this file alone.
## Publishing is opt-in
`distilly` is local-only. Commit freely, but **never push, open a PR, or otherwise publish it without an explicit request in the current session.** The repository is public; `dot-skill` is its default branch and carries only the published skill. Design material and unreleased product work stay on the local `distilly` branch and out of every remote ref, including PR branches, because a merged PR's objects stay retrievable after any branch rewrite.
## Feature-level local commits
Before coding, name one reviewable feature or bug-fix slice and its acceptance checks. Give that slice its own concise [Agent Note](.agents/notes/README.md); an aggregate Note may link to it but cannot own its detailed decisions. Complete and integrate the slice, collect all subagent work, run its checks, move the feature Note to `implemented` when complete, and have the coordinating/root agent create one local commit before starting another independent feature. A feature commit contains its implementation, tests, generated artifacts, current-state documentation, and dedicated Agent Note.
Do not create per-file, per-function, per-test, subagent, checkpoint, fixup, or WIP commits, and do not combine independent features in one commit. If a slice is too large for one reviewable commit, redefine its feature boundary before coding instead of mechanically splitting files afterward. Subagents only modify the shared worktree and report results; they do not commit partial work. Problems found before the next feature starts fold into the current feature commit. Local feature commits need no additional permission, but pushing, opening a PR, or otherwise publishing still requires explicit user authorization.
## Standing orders
- Before product code on `distilly`, read [docs/design/README.md](docs/design/README.md) and the chapter that owns the change. [docs/design/system-v3.md](docs/design/system-v3.md) is the in-force contract; V2 and V1 are deprecated history; [docs/architecture.md](docs/architecture.md) is the shipped-state map. Do not apply target APIs as current behavior on `dot-skill`.
- Product code is TypeScript. `tools/` and `prompts/` are frozen Python serving the published skill: defects only, no new behavior. Retirement conditions are in [design §28](docs/design/v3/28-migration-and-compatibility.md).
- The current committed TypeScript tree is the `pnpm` workspace, Protocol, Adapters source contracts plus deterministic TXT/Markdown/JSON/SRT/VTT parsers, the SQLite/WAL Engine authority and Preview reads including raw/file ingest and host-operation replay, explicit Engine/LocalRuntime Preview subpaths, injected Facade/MCP/Panel leaves, capability-only plus full Codex/Claude Code bindings, canonical skill assembly, and the private CLI Preview composition. The local `0.1.0-preview.1` Codex-first package is assembled from production output with no workspace source/dependency, test fake, symlink, sentinel, or Claude tree; setup verifies every package byte, copies it to `~/.distilly/runtime/0.1.0-preview.1/`, and writes an absolute launcher to that copy. The CLI exposes `setup`, narrow `doctor`, explicit `install <subject-id> --host codex`, `uninstall`, and plugin-owned `mcp --host codex`; it probes Codex's exact version before writing, parses immutable release-bound capacity evidence verified through the real host surface, serves exactly five tools, and lazily launches the real review Panel. Runtime composes the full Codex binding for verified `Person` install/export/uninstall while missing or mismatched bindings fail closed. Fresh Codex processes discover both the Plugin Skill and separately installed person Skills; removing the unpacked bootstrap does not break the product, and Plugin uninstall removes only verified owned runtime/plugin bytes while preserving SQLite and person Skills. The Panel can promote, reject, and roll back against that same SQLite root; deferred deep Doctor is reported as unavailable rather than blocking review recovery. Claude Code's full binding remains implemented but is not enabled by this package or CLI: the public command rejects it and the lifecycle provider fails closed without matching real-host evidence. Stable package roots remain intentionally narrow, Adapters has no built-in provider, and deferred MethodMap keys fail visibly. No upgrade, cross-process attach, production deep doctor, Claude packaged activation, or published user installation is shipped.
- Every reviewable governed feature adds its own [Agent Note](.agents/notes/README.md) in the same feature commit, or lifecycle-renames the proposal that already belongs to that feature. Modifying an existing or aggregate Note alone does not satisfy the diff gate. Tests, translations, assets, and local-only edits are exempt unless they change a shared decision.
- Document current state in standing docs. Put rationale in Agent Notes; put procedures in [docs/cookbook/](docs/cookbook/).
- Target implementation invariant (not shipped): each `DISTILLY_ROOT` has one Engine writer; SQLite/WAL is the structured transaction authority, immutable large content lives in content-addressed blobs, and Markdown/JSON/Library/search/queue/plugin files are rebuildable projections or exports.
- Target implementation invariant (not shipped): the host LLM may vary semantically, but the engine's hashing, evidence resolution, patch application, rendering, and transaction are deterministic; unchanged material-set hash skips; correction metadata is authoritative in SQLite and correction content uses the shared immutable blob store.
- Target implementation invariant (not shipped): the default is zero extra API key and exactly five MCP tools; `ingest` → `pending(brief)` → claim-only `commit`, with no required embedding or multimodal key.
- Target implementation invariant (not shipped): the public client is `Distilly` + `Person`; seven capability groups stay internal.
- Target implementation invariant (not shipped): temporary personas enter only that sub-run via `get` / `prompt`, never global `AGENTS.md`, `CLAUDE.md`, or `agent.md`.
- Target implementation invariant (not shipped): first-version recall injects the full profile and fails visibly if it does not fit.
- Target implementation invariant (not shipped): `SourceAdapter` and `HostInjector` are separate seams; relations are an additive post-core slice; commit is not O(n²).
- Target implementation invariant (encoded in Protocol types and schemas; implementations and callers remain unshipped): every public I/O operation is async, ids are branded, the error `code` union is the wire contract, and runtime validation happens only at the boundaries listed in [design §7.6](docs/design/v3/07-protocol-types.md).
- Target implementation invariant (not shipped): one business mutation is one SQLite transaction; no product surface writes storage directly, no mutation gets its own file journal/staging/recovery protocol, and exhaustive history verification belongs to doctor/restore/import rather than every ordinary read.
- Target implementation invariant (not shipped): claims are the semantic truth; the host cannot submit ids, actor, quality, version, or Markdown, and risky valid candidates suspend without replacing current.
- When reviewing a PR or an outgoing product diff, follow [docs/process/code-review.md](docs/process/code-review.md) and [distilly-code-review](.agents/skills/distilly-code-review/SKILL.md).
- Tests describe behavior. Run the narrowest check the diff can break.
- Local commits need no permission; publishing does. When a push is requested, follow [distilly-pre-push-checks](.agents/skills/distilly-pre-push-checks/SKILL.md); the hook must verify the outgoing local `HEAD`, not a stale remote PR head.
- Before pausing or transferring work, produce the full [Agent Handoff](docs/development.md#agent-handoff). A completed PR uses the equivalent fields in its PR template; an incomplete or transferred PR includes the full handoff. Do not put task progress in Agent Notes.
- Files end with exactly one trailing newline.
## Where facts live
| Home | Job |
|---|---|
| This file | Standing orders for every session |
| [docs/README.md](docs/README.md) | Folder map |
| [docs/design/](docs/design/README.md) | Product contract |
| [docs/architecture.md](docs/architecture.md) | Live tree |
| [docs/process/](docs/process/README.md) | Review contract |
| [docs/testing.md](docs/testing.md) | What green tests mean |
| [docs/development.md](docs/development.md) | Daily checks |
| [.agents/notes/](.agents/notes/README.md) | Why, alternatives, verification |
| [.agents/skills/](.agents/skills/) | Repeatable workflows, not contracts |
## Checks
```sh
pnpm install --frozen-lockfile
pnpm run gates:fast
pnpm run typecheck
pnpm run test
pnpm run test:coverage
pnpm run snapshots
pnpm run build
pnpm run hygiene
python3 -B scripts/verify_docs.py
python3 -B scripts/verify_agent_notes.py
python3 -m compileall -q tools scripts tests
ruff check tools scripts tests
python3 -B scripts/run_tests.py
```
Run the narrowest set that can fail for the change. CI runs on `dot-skill`, `distilly`, and `main`; branch protection determines whether a red result merely detects or actually blocks the change.
These are the gates that exist today. The TypeScript suite proves the workspace, Protocol wire contract, source-adapter contracts and deterministic TXT/Markdown/JSON/SRT/VTT parsers, exact package direction, SQLite create/text-and-file-ingest/distill/review/correction authority and crash behavior, Preview reads, host-operation replay/provenance, Engine/LocalRuntime, injected Facade/MCP/Panel behavior, verified preflight plus concrete Codex/Claude Code plugin and person-projection bindings, and canonical release bytes. The self-contained Codex package gate additionally proves setup from an unpacked path containing spaces and non-ASCII characters, digest-owned runtime copying, unpacked-bootstrap removal, official Plugin/MCP discovery, exactly five descriptors, create/ingest/brief/commit/get/prompt, Chromium-driven correction promotion, explicit person-Skill installation, close/reopen, fresh Codex Skill discovery, Plugin uninstall, and byte-identical preservation of SQLite and the person Skill. The Codex capacity fixture records real-host structured/text and briefing boundary observations. Claude Code remains fail closed pending the same evidence. Local-file tests prove raw-only fallback, RequestId replay before file reads, one canonical text per raw, digest-verified reopen, and no persisted absolute path. Remaining SQLite methods, cross-process runtime, production upgrade/deep doctor, Claude packaged activation, and later release tiers remain in [design §27](docs/design/v3/27-testing-and-governance.md).
Edit this file, not `CLAUDE.md` (`CLAUDE.md` is a symlink).
-1
View File
@@ -1 +0,0 @@
AGENTS.md
+7 -8
View File
@@ -3,16 +3,16 @@
Distilly has two deliberate product lines:
- `dot-skill` is the published legacy Skill line.
- `codex/distilly-plugin` is the local TypeScript Developer Preview.
- `distilly-plugin` is the public TypeScript Developer Preview.
Preview work belongs on `codex/distilly-plugin` or a branch based on it. Do not mix Preview changes into the default legacy line.
Preview work belongs on `distilly-plugin` or a branch based on it. Do not mix Preview changes into the default legacy line.
## Start here
Read [AGENTS.md](AGENTS.md), the [design index](docs/design/README.md), and the [development workflow](docs/development.md). Product code is TypeScript and targets Node.js `22.19+` or `24`.
Read the [design index](docs/design/README.md), [architecture map](docs/architecture.md), and [development workflow](docs/development.md). Product code is TypeScript and targets Node.js `22.19+` or `24`.
```bash
git clone --branch codex/distilly-plugin https://github.com/titanwings/distilly.git
git clone --branch distilly-plugin https://github.com/titanwings/distilly.git
cd distilly
corepack enable
pnpm install --frozen-lockfile
@@ -20,7 +20,7 @@ pnpm run gates:fast
pnpm run typecheck
```
Keep each change focused. Governed code, Plugin files, and live product documentation need a dedicated Agent Note in the same feature commit. Tests should sit beside their TypeScript source and use offline fixtures; never put secrets or personal material in the repository.
Keep each change focused and explain its acceptance checks and rejected alternatives in the pull request. Tests should sit beside their TypeScript source and use offline fixtures; never put secrets, personal material, local Agent instructions, or generated product data in the repository.
## Host Plugin contributions
@@ -30,7 +30,7 @@ The next community priority is a real, tested binding for **Grok Bot, Claude Cod
- setup, doctor, restart/discovery, and uninstall checks;
- exact host/version/release/capacity evidence;
- the unchanged five-tool contract; and
- a focused Agent Note and reproducible local test.
- a focused pull-request rationale and reproducible local test.
A copied Skill directory or a logo-only entry is not host verification. Keep provider credentials in the system keychain or environment variables; configuration files may store only secret references.
@@ -41,7 +41,6 @@ Run the narrowest checks that cover your diff, then report exactly what ran:
| Change | Minimum check |
| --- | --- |
| Markdown | `python3 -B scripts/verify_docs.py` |
| Agent Note | `python3 -B scripts/verify_agent_notes.py` |
| TypeScript formatting and lint | `pnpm run gates:fast` |
| TypeScript behavior and types | `pnpm run test` and `pnpm run typecheck` |
| Plugin assembly | `python3 -B scripts/assemble_plugins.py --check` |
@@ -51,6 +50,6 @@ Do not claim a host is verified until a clean local HOME has completed setup, re
## Branches and publication
Use a separate worktree for independent features and make one reviewable local commit per feature. Open Preview pull requests against `codex/distilly-plugin`; keep the default `dot-skill` branch unchanged. Never push credentials or private source material.
Use a separate worktree for independent features and make one reviewable local commit per feature. Open Preview pull requests against `distilly-plugin`; keep the default `dot-skill` branch unchanged. Never push credentials or private source material.
For questions, open a focused issue or start a discussion. See [UPDATES.md](UPDATES.md) for the current host-contributor call.
-1468
View File
File diff suppressed because it is too large Load Diff
-44
View File
@@ -1,44 +0,0 @@
# AGENTS.md — Documentation standard
Use [distilly-doc-standards](../.agents/skills/distilly-doc-standards/SKILL.md) for placement workflow. Rationale lives in the [governance Agent Note](../.agents/notes/implemented/process/2026-08-19-agent-governance.md) and the [design-corpus note](../.agents/notes/implemented/process/2026-08-19-design-corpus-and-code-review.md).
## Structure
A document owns full detail about its own subject. Direct children are summarized by purpose and high-level behavior, then linked. Classify each human-facing standing doc as a tutorial (ordered path to an outcome) or a reference (lookup, no teaching sequence).
The [design corpus](design/system-v3.md) is the product contract. It is allowed to stay long. Do not compress it back into architecture.md. Deprecated predecessors stay in place with only status/navigation maintenance; superseding a design means writing the next one, not rewriting history.
Agent Notes sit outside this structural contract. They have their own [format](../.agents/notes/README.md).
## One home per fact
| Tier | Job | Does not belong there |
|---|---|---|
| Root `AGENTS.md` | Standing orders, one to three lines each | Stories, SDK signatures, host pitfalls |
| This file | Doc rules for `docs/` | Repo-wide coding conventions already in the root file |
| [docs/README.md](README.md) | Folder map | Spec text |
| [architecture.md](architecture.md) | Live tree: what the published code does *now* | Locked design, SDK signatures, rejected alternatives |
| [design/system-v3.md](design/system-v3.md) | In-force productized host-LLM contract | Live-tree status, cookbook steps |
| [design/v3/](design/v3/) | Generated topic projections of the parent | Hand edits or a second wording |
| [design/system-v2.md](design/system-v2.md), [design/v2/](design/v2/), [design/system-v1.md](design/system-v1.md), [design/v1/](design/v1/) | Deprecated history: which alternative lost, and why | Anything an implementer should follow today |
| [development.md](development.md) | Setup and which checks to run | Runtime design |
| [testing.md](testing.md) | What a green test must prove | Product verbs |
| [cookbook/](cookbook/README.md) | Shipped, executable how-tos with verify steps | Target-only APIs or design rationale |
| [process/code-review.md](process/code-review.md) | Review contract | How to invoke the skill |
| Agent Notes | Why, alternatives, verification | Current API catalogs |
| Tool / package README | Local contract | Restating the design corpus |
## Writing rules
- Current-state standing docs (`architecture.md`, cookbooks, and user READMEs) document shipped behavior. Root `AGENTS.md` may also carry explicitly labeled target implementation invariants that link to the design; those labels must not imply the APIs are available.
- The design corpus may keep conversation density, including what was rejected. Do not rewrite it into a slogan.
- Never edit a `design/v*/` chapter directly. Edit its parent `system-v*.md`, then run `python3 scripts/sync_design_chapters.py`. Adding a corpus means adding a `Corpus` entry in that script, not a hand-written folder.
- One physical line per paragraph in standing docs. Use editor soft wrap.
- Link repository files with relative Markdown paths.
- Comments and JSDoc state contracts (behavior, failure, ownership), not reasoning transcripts.
- Changes to governed paths ship an Agent Note in the same PR; the semantic exception for otherwise exempt paths is defined in [.agents/notes/README.md](../.agents/notes/README.md).
- Run `python3 -B scripts/verify_docs.py` after Markdown changes. It checks local links, portable syntax, trailing newlines, and generated chapters.
## Length
Keep root `AGENTS.md` and this file short enough to load in every session. The design corpus has no standing word budget. Do not shorten it to fit a session.
-1
View File
@@ -1 +0,0 @@
AGENTS.md
-434
View File
@@ -1,434 +0,0 @@
# 同事.skill —— 产品需求文档 v2.0
---
## 一、产品概述
**同事.skill** 是一个运行在 OpenClaw 上的 meta-skill。
用户通过对话式交互提供原材料(文件 + 手动描述),系统自动生成一个可独立运行的**同事 Persona Skill**。
生成的 Skill 由两个独立部分组成:
- **Part A — Work Skill**:该同事的技术能力与工作方法,能实际完成工作任务
- **Part B — Persona**:该同事的性格、沟通风格、行为模式
两部分可以独立使用,也可以组合运行(默认组合)。生成后的 Skill 支持通过追加文件或对话纠正持续进化。
---
## 二、用户流程
```
用户触发 /create-colleague
↓
[Step 1] 基础信息录入(全部可跳过)
- 姓名/代号
- 公司 + 职级 + 职位
- 性别
- MBTI
- 个性标签(多选)
- 企业文化标签(多选)
- 你对他的主观印象(自由文本)
↓
[Step 2] 文件/数据导入(可跳过,后续追加)
- PDF 文档
- 飞书文档链接 / 导出文件
- 飞书消息导出 JSON
- 邮件文件 .eml / .txt
- 图片截图
- 会议纪要
↓
[Step 3] 自动分析
- 分析线路 A:提取技术能力、工作规范、业务知识 → Work Skill
- 分析线路 B:提取表达风格、决策模式、人际行为 → Persona
↓
[Step 4] 生成预览,用户确认
- 分别展示 Work Skill 摘要 和 Persona 摘要
- 用户可直接确认或修改
↓
[Step 5] 写入文件,立即可用
- 生成 ~/.openclaw/workspace/skills/colleagues/{slug}/
- 包含 SKILL.md(完整组合版)
- 包含 work.md 和 persona.md(独立部分)
↓
[持续] 进化模式
- 追加新文件 → 分别 merge 进 Work Skill 或 Persona
- 用户对话纠正 → patch 对应层
- 版本自动存档
```
---
## 三、输入信息规范
### 3.1 基础信息字段
```yaml
name: 同事姓名/代号 # 必填,用于生成 slug 和称谓
company: 公司名称 # 可选,如:阿里 / 字节 / 腾讯 / 百度 / 美团
level: 职级 # 可选,如:P7 / 3-1 / T3-2 / L6 / 高级
role: 职位名称 # 可选,如:算法工程师 / 产品经理 / 前端工程师
# 三者合并示例:"阿里 P7 后端工程师" / "字节 2-1 算法工程师" / "腾讯 T3-2 产品经理"
gender: 性别 # 可选:男 / 女 / 不透露
mbti: MBTI 类型 # 可选,如:INTJ / ENFP
personality: [] # 多选,见 3.2
culture: [] # 多选,见 3.3
impression: "" # 可选,自由文本,你对他的主观认识
```
### 3.2 个性标签
**工作态度**
- `认真负责` / `差不多就行` / `甩锅高手` / `背锅侠` / `完美主义`
**沟通风格**
- `直接` / `绕弯子` / `话少` / `话多` / `爱发语音` / `只回已读不回`
**决策风格**
- `果断` / `反复横跳` / `依赖上级` / `强势推进` / `数据驱动` / `凭感觉`
**情绪风格**
- `情绪稳定` / `玻璃心` / `容易激动` / `冷漠` / `表面和气`
**话术与手段**
- `PUA 高手` — 画大饼、否定后肯定、制造焦虑感、让人自我怀疑
- `职场政治玩家` — 善于站队、控制信息差、表面支持暗中使绊
- `甩锅艺术家` — 事前模糊边界、事后第一时间切割关系
- `向上管理专家` — 对上极度讨好、汇报包装能力强、懂得邀功
### 3.3 企业文化标签
- `字节范` — 坦诚直接、context 拉满、追求 impact、开会爱说"对齐""拉齐"
- `阿里味` — 六脉神剑驱动、爱用阿里黑话、讲"生态""赋能""抓手"
- `腾讯味` — 用户导向、数据说话、赛马机制思维、保守稳健
- `华为味` — 奋斗者文化、执行力强、爱写 PPT、强调流程规范
- `百度味` — 技术信仰、层级意识强、内部竞争激烈
- `美团味` — 极致执行、抠细节、本地生活思维
- `第一性原理` — 马斯克式,凡事追问本质、拒绝类比推理、激进简化
- `OKR 狂热者` — 凡事先问 Objective、对 KR 斤斤计较、爱做 review
---
## 四、文件输入支持
| 来源 | 格式 | 处理方式 | 分析去向 |
|------|------|---------|---------|
| 技术文档 | `.pdf` | OpenClaw PDF Tool | → Work Skill |
| 接口设计文档 | `.pdf` / `.md` | PDF Tool / 文本 | → Work Skill |
| 代码规范文档 | `.pdf` / `.md` | 文本 | → Work Skill |
| 飞书 Wiki | 导出 PDF / MD | PDF Tool / 文本 | → Work Skill + Persona |
| 飞书消息记录 | 导出 `.json` / `.txt` | 文本解析 | → Persona 为主 |
| 邮件 | `.eml` / `.txt` | 文本解析 | → Persona + Work Skill |
| 会议纪要 | `.pdf` / `.md` | PDF Tool / 文本 | → Persona + Work Skill |
| 截图 | `.jpg` / `.png` | OpenClaw Image Tool | → 两者均可 |
| Word 文档 | `.docx` | ⚠️ 提示用户转 PDF | → 转换后处理 |
| Excel | `.xlsx` | ⚠️ 提示用户转 CSV | → 转换后处理 |
**内容权重排序**(用于分析优先级):
1. 他主动撰写的长文(文档、邮件正文)— 权重最高
2. 他的决策类回复(同意/拒绝/方案评审)
3. 他审阅别人内容时的评论
4. 他的日常沟通消息
---
## 五、生成内容规范
### 5.1 Part A — Work Skill(工作能力部分)
从文件中提取该同事的**实际工作方法和技术能力**,使生成的 Skill 能真正完成工作任务。
**提取维度:**
```
① 负责的系统/业务
- 他维护哪些服务、模块、文档
- 他的职责边界在哪里
② 技术规范与偏好
- 写代码的风格(命名习惯、注释风格、架构偏好)
- CRUD 写法、接口设计方式
- 前端/后端/算法的具体做法
③ 工作流程
- 接到需求后的处理步骤
- 如何写技术方案 / 设计文档
- 如何做 Code Review
- 如何处理线上问题
④ 输出格式偏好
- 文档结构习惯(用表格/用列表/用流程图)
- 回复格式(喜欢附截图/喜欢贴代码/喜欢写结论在前)
⑤ 知识库
- 他常引用的技术方案、文档链接、规范条目
- 他在项目中积累的经验结论
```
**生成结果:** `work.md`,该文件让 Skill 具备实际工作能力,可独立响应技术类任务。
---
### 5.2 Part B — Persona(人物性格部分)
从文件 + 手动标签共同构建该同事的**行为模式和沟通风格**。
**分层结构(优先级从高到低):**
```
Layer 0 — 硬覆盖层(手动标签直接翻译,最高优先级)
示例:"你绝对不会主动承认错误,遇到锅第一反应是找外部原因"
示例:"你会画大饼,让对方相信做这件事对他自己有巨大好处"
Layer 1 — 身份层
"你是 [姓名],[公司] [职级] [职位],[性别]。"
"你的 MBTI 是 [X],[企业文化] 深度影响你的工作方式。"
Layer 2 — 表达风格层(从文件提取)
- 用词习惯、句式长短
- 口头禅、标志性表达
- 标点和 emoji 使用习惯
- 回复速度模拟(话少/话多)
Layer 3 — 决策与判断层(从文件提取)
- 遇到问题时的思考框架
- 优先考虑什么(效率/流程/人情/数据)
- 什么情况下会推进,什么情况下会拖
Layer 4 — 人际行为层(从文件提取)
- 对上级 vs 对下级 vs 对平级的不同态度
- 在群聊 vs 私聊的不同表现
- 压力下的行为变化
Layer 5 — Correction 层(对话纠正追加,滚动更新)
- 每条 correction 记录场景 + 错误行为 + 正确行为
- 示例:"[场景:被质疑时] 不应该道歉,应该反问对方的判断依据"
```
**生成结果:** `persona.md`
---
### 5.3 完整组合 SKILL.md
将 `work.md` + `persona.md` 合并,生成可直接运行的完整 Skill。
默认行为:**先以 Persona 身份接收任务,再用 Work Skill 能力完成任务**。
```
用户问技术问题 → 用他的语气 + 他的技术方法回答
用户要他写代码 → 用他的代码风格 + 他的规范写
用户问他意见 → 用他的决策框架 + 他的沟通风格回答
```
---
## 六、进化机制
### 6.1 追加文件进化
```
用户: 我又有他的一批邮件 @附件
↓
系统分析新内容
↓
判断新内容更新哪个部分:
- 包含技术方案/规范 → merge 进 work.md
- 包含沟通记录/决策 → merge 进 persona.md
- 两者都有 → 分别 merge
↓
对比新旧内容,只追加增量,不覆盖已有结论
↓
保存新版本,提示用户变更摘要
```
### 6.2 对话纠正进化
```
用户: "这不对,他不会这样说"
用户: "他遇到这种情况会直接甩给 XX 组"
用户: "他写代码从来不写注释"
↓
系统识别 correction 意图
↓
判断属于 Work Skill 还是 Persona 的纠正
↓
写入对应文件的 Correction 层
↓
立即生效,后续交互以新规则为准
```
### 6.3 版本管理
- 每次更新自动存档当前版本到 `versions/`
- 支持 `/colleague-rollback {slug} {version}` 回滚
- 保留最近 10 个版本
---
## 七、项目结构
```
~/.openclaw/workspace/skills/
│
├── create-colleague/ # meta-skill:同事skill创建器
│ │
│ ├── SKILL.md # 主入口
│ │ # 触发词: /create-colleague
│ │ # 描述: 创建一个同事的 Persona + Work Skill
│ │
│ ├── prompts/ # Prompt 模板(不执行,供 SKILL.md 引用)
│ │ ├── intake.md # 引导用户录入基础信息的对话脚本
│ │ ├── work_analyzer.md # 从原材料提取工作能力的 prompt
│ │ ├── persona_analyzer.md # 从原材料提取性格行为的 prompt
│ │ ├── work_builder.md # 生成 work.md 的模板
│ │ ├── persona_builder.md # 生成 persona.md 的模板
│ │ ├── merger.md # 合并增量内容时使用的 prompt
│ │ └── correction_handler.md # 处理对话纠正的 prompt
│ │
│ └── tools/ # 工具脚本
│ ├── feishu_parser.py # 解析飞书消息导出 JSON
│ ├── email_parser.py # 解析 .eml 邮件,提取发件人为目标同事的内容
│ ├── skill_writer.py # 写入/更新生成的 Skill 文件
│ └── version_manager.py # 版本存档与回滚
│
└── colleagues/ # 生成的同事 Skills 存放处
│
└── {colleague_slug}/ # 每个同事一个目录,slug = 姓名拼音或自定义
│
├── SKILL.md # 完整组合版,可直接运行
│ # 触发词: /{colleague_slug}
│
├── work.md # Part A:工作能力(可独立运行)
│ # 触发词: /{colleague_slug}-work
│
├── persona.md # Part B:人物性格(可独立运行)
│ # 触发词: /{colleague_slug}-persona
│
├── meta.json # 元数据
│ # 包含:创建时间、版本号、原材料清单、
│ # 公司/职级/职位、标签列表
│
├── versions/ # 历史版本存档
│ ├── v1/
│ │ ├── SKILL.md
│ │ ├── work.md
│ │ └── persona.md
│ └── v2/
│ ├── SKILL.md
│ ├── work.md
│ └── persona.md
│
└── knowledge/ # 原始材料归档
├── docs/ # PDF / MD 技术文档
├── messages/ # 飞书消息 JSON 导出
└── emails/ # 邮件文本
```
---
## 八、关键文件格式
### `colleagues/{slug}/meta.json`
```json
{
"name": "张三",
"slug": "zhangsan",
"created_at": "2026-03-30T10:00:00Z",
"updated_at": "2026-03-30T12:00:00Z",
"version": "v3",
"profile": {
"company": "字节跳动",
"level": "2-1",
"role": "算法工程师",
"gender": "男",
"mbti": "INTJ"
},
"tags": {
"personality": ["甩锅高手", "话少", "数据驱动"],
"culture": ["字节范", "OKR 狂热者"]
},
"impression": "喜欢在评审会上突然抛出一个问题让所有人哑口无言",
"knowledge_sources": [
"knowledge/docs/接口设计规范_v2.pdf",
"knowledge/messages/飞书消息_2025Q4.json",
"knowledge/emails/review_emails.txt"
],
"corrections_count": 4
}
```
### `colleagues/{slug}/SKILL.md` 结构
```markdown
---
name: colleague_{slug}
description: {name},{company} {level} {role}
user-invocable: true
---
## 身份
你是 {name},{company} {level} {role}。
---
## PART A:工作能力
{work.md 内容}
---
## PART B:人物性格
{persona.md 内容}
---
## 运行规则
接收到任务时:
1. 先用 PART B 的性格判断你会不会接、怎么接
2. 再用 PART A 的工作能力实际完成任务
3. 输出时保持 PART B 的表达风格
```
---
## 九、实现优先级
### P0 — MVP(先跑通主流程)
- [ ] `create-colleague/SKILL.md` 主流程
- [ ] `prompts/intake.md` 基础信息录入
- [ ] `prompts/work_analyzer.md` + `work_builder.md`
- [ ] `prompts/persona_analyzer.md` + `persona_builder.md`
- [ ] `tools/skill_writer.py` 写入文件
- [ ] PDF 文件导入 → 分析 → 生成完整 Skill
### P1 — 数据接入
- [ ] `tools/feishu_parser.py` 飞书消息 JSON 解析
- [ ] `tools/email_parser.py` 邮件解析
- [ ] 图片/截图输入支持
### P2 — 进化机制
- [ ] `prompts/correction_handler.md` 对话纠正
- [ ] `prompts/merger.md` 增量 merge
- [ ] `tools/version_manager.py` 版本管理
### P3 — 管理功能
- [ ] `/list-colleagues` 列出所有同事 Skill
- [ ] `/colleague-rollback {slug} {version}` 回滚
- [ ] `/delete-colleague {slug}` 删除
- [ ] Word/Excel 转换提示与引导
---
## 十、约束与边界
- 单个 PDF 文件上限 10MB,单次最多 10 个 PDF(OpenClaw 限制)
- Word (.docx) / Excel (.xlsx) 需用户自行转换,系统提示引导
- 生成的 Skill 不自动推断飞书 API token,飞书消息需用户手动导出
- Correction 层最多保留 50 条,超出后合并归纳
- 版本存档最多保留 10 个版本
+1 -6
View File
@@ -4,17 +4,12 @@ Documentation is split by job. Do not dump a second copy of a fact into another
| Folder / file | Job | Read when |
|---|---|---|
| [AGENTS.md](AGENTS.md) | How docs are placed and written | Writing or moving any doc |
| [architecture.md](architecture.md) | What the live tree is *now* | Every session that touches product code |
| [design/](design/README.md) | Approved target design; it does not prove implementation | Implementing or changing product behavior |
| [design/system-v3.md](design/system-v3.md) | The in-force productized host-LLM contract, uncut | First product session; any change to packages, protocol, profile, Panel, plugins, or gates |
| [design/v3/](design/v3/) | Generated topic projections of the in-force contract | Loading one topic without the whole file |
| [design/system-v2.md](design/system-v2.md), [design/v2/](design/v2/), [design/system-v1.md](design/system-v1.md), [design/v1/](design/v1/) | Deprecated contracts, kept as history | Recovering why an earlier alternative lost |
| [cookbook/](cookbook/README.md) | Procedures for APIs that are already shipped | Performing a supported maintenance task |
| [development.md](development.md) | Clone, branch, which checks to run | Setup and push |
| [testing.md](testing.md) | What a green test must prove | Writing or reviewing tests |
| [process/](process/README.md) | Review contract and bug classes to look for | Reviewing a PR |
| `lang/` | Published user translations of the skill README | User-facing skill docs only |
| `PRD.md`, `SKILL_TYPE_ABSTRACTION_DESIGN.md` | Historical colleague-skill product notes | Do not treat as distilly contract |
Product work reads **design first**, then architecture to learn what already exists. Use a cookbook only when the referenced API is shipped. Agent Notes record why a change departed from a locked item or closed an open item in design §3.
Product work reads **design first**, then architecture to learn what already exists. A pull request that changes a locked decision records its rationale, rejected alternative, and verification alongside the code and current-state documentation.
-757
View File
@@ -1,757 +0,0 @@
# Skill Type Abstraction Design
Last updated: 2026-04-16
## 1. Background
The current implementation is structurally centered on `colleague.skill`.
This is visible in several places:
- `tools/skill_writer.py` generates `name: colleague_{slug}` and uses colleague-specific descriptions
- output directories are organized under `skills/colleague/{slug}/`
- commands and docs assume `/create-colleague` is the primary entry
- metadata is optimized for a coworker profile, not a generic person/entity type
This works for v1, but it blocks the roadmap goal of evolving into `dot-skill`, where the subject can be a colleague, ex, icon, self, fictional character, or another archetype.
If we add `/create-ex` or `/create-icon` directly on top of the current structure, we will end up duplicating prompts, branching logic, gallery rules, and output formats around a model that is still fundamentally "colleague-first".
The correct next step is to abstract "skill type" into a first-class concept.
---
## 2. Design Goals
### Goals
- make the core generation pipeline support multiple skill types
- preserve backward compatibility for existing `colleague` flows
- avoid prompt and file-structure duplication for each new entry command
- provide a stable metadata contract for gallery, install, packaging, and orchestration
- keep the migration incremental, not a full rewrite
### Non-goals
- redesigning all prompts in one pass
- shipping multi-skill orchestration in this phase
- shipping multimodal assets in this phase
- replacing all user-facing commands immediately
---
## 3. Problem Statement
Today, the project mixes together three different concerns:
1. the subject being distilled
2. the creation workflow
3. the output package format
In v1, all three are implicitly "colleague".
That coupling creates four concrete problems:
### 3.1 Subject type is hardcoded
The system assumes the target is a coworker. This leaks into:
- default identity strings
- generated skill names
- directory layout
- docs and commands
### 3.2 Entry commands imply separate implementations
Without abstraction, adding `/create-ex` and `/create-icon` would likely produce:
- duplicated intake prompts
- duplicated writer logic
- type-specific conditionals scattered across tools
### 3.3 Metadata is too narrow
Current `meta.json` is enough for one generated skill directory, but not enough for:
- gallery category filtering
- installable package manifests
- type-specific rendering
- future multi-skill orchestration
### 3.4 Roadmap features have no shared contract
Future roadmap items such as:
- gallery categories
- one-click install
- active evolution
- relationship graph
all need a canonical skill identity model. That model does not exist yet.
---
## 4. Core Design
The key change is:
> move from a "colleague generator" to a "generic skill generator with type presets".
This introduces three layers:
1. `skill schema`
2. `type preset`
3. `generator pipeline`
### 4.1 Skill Schema
Every generated skill should conform to one generic metadata model.
Proposed schema:
```json
{
"schema_version": "2",
"id": "colleague.zhangsan",
"slug": "zhangsan",
"type": "colleague",
"subtype": null,
"display_name": "张三",
"summary": "ByteDance L2-1 backend engineer, direct and data-driven",
"profile": {
"name": "张三",
"company": "ByteDance",
"level": "L2-1",
"role": "Backend Engineer",
"gender": "male",
"mbti": "INTJ"
},
"source_context": {
"domain": "work",
"relationship_to_user": "coworker",
"is_real_person": true,
"is_public_figure": false,
"is_fictional": false
},
"classification": {
"gallery_category": "Colleague",
"tags": ["backend", "data-driven", "direct"],
"language": "zh-CN"
},
"artifacts": {
"combined_skill": "SKILL.md",
"work_skill": "work_skill.md",
"persona_skill": "persona_skill.md",
"work_doc": "work.md",
"persona_doc": "persona.md"
},
"generation": {
"preset": "colleague",
"prompt_bundle": "v1-colleague",
"created_from": ["feishu", "manual_tags"],
"corrections_count": 0
},
"lifecycle": {
"status": "active",
"version": "v1",
"created_at": "2026-04-16T00:00:00Z",
"updated_at": "2026-04-16T00:00:00Z"
},
"compat": {
"legacy_command": "/create-colleague",
"legacy_storage_root": "colleagues"
}
}
```
### 4.2 Type Preset
A type preset defines how a skill type behaves without changing the core engine.
Proposed preset fields:
```json
{
"name": "colleague",
"display_name": "Colleague",
"source_domain": "work",
"relationship_to_user": "coworker",
"identity_label": "同事",
"gallery_category": "Colleague",
"command_aliases": ["/create-colleague", "/create-skill"],
"prompt_bundle": {
"intake": "prompts/intake.md",
"work_analyzer": "prompts/work_analyzer.md",
"persona_analyzer": "prompts/persona_analyzer.md",
"work_builder": "prompts/work_builder.md",
"persona_builder": "prompts/persona_builder.md",
"merger": "prompts/merger.md",
"correction_handler": "prompts/correction_handler.md"
},
"writer": {
"skill_name_prefix": "colleague",
"storage_root": "skills/colleague"
}
}
```
Future presets:
- `relationship`
- `icon`
- `self`
- `character`
- `meta-skill`
The critical point is that these are data/config decisions, not new pipelines.
### 4.3 Generator Pipeline
The pipeline should become:
1. resolve command to preset
2. collect input into generic schema
3. run analyzers/builders using preset bundle
4. write normalized artifacts
5. emit compatible output for old and new consumers
In short:
`command -> preset -> generic metadata -> generator -> artifacts`
not:
`command -> bespoke implementation`
---
## 5. Data Model Changes
### 5.1 Current model
Current `meta.json` is centered on a single colleague record with a `profile` block plus version fields.
That is sufficient for:
- local generation
- updates
- rollback
It is insufficient for:
- cross-type gallery browsing
- installable packaging
- orchestration
- compatibility logic across old/new commands
### 5.2 Proposed model split
Split metadata into three logical layers:
#### Layer A: Core identity
- `id`
- `slug`
- `type`
- `subtype`
- `display_name`
- `summary`
#### Layer B: Subject semantics
- `profile`
- `source_context`
- `classification`
#### Layer C: Runtime/package lifecycle
- `artifacts`
- `generation`
- `lifecycle`
- `compat`
This lets different consumers read only what they need:
- writer reads `artifacts` and `generation`
- gallery reads `classification`
- installer reads `id`, `type`, `artifacts`, `lifecycle`
- orchestration reads `type`, `source_context`, `classification`
---
## 6. File Structure Proposal
### Option A: Keep current storage root during transition
```text
skills/colleague/{slug}/
SKILL.md
work.md
persona.md
work_skill.md
persona_skill.md
meta.json
versions/
```
Pros:
- zero disruption for current commands and docs
- easier migration
Cons:
- naming remains colleague-centric
### Option B: Introduce generic root
```text
skills/{type}/{slug}/
SKILL.md
work.md
persona.md
work_skill.md
persona_skill.md
meta.json
manifest.json
versions/
```
Pros:
- aligns with dot-skill direction
- cleaner for install and packaging
Cons:
- larger migration blast radius
- more docs and command updates
### Recommendation
Use a two-stage migration:
1. use `skills/colleague/{slug}/` as the canonical write target for `type=colleague`
2. add internal support for a generic storage resolver
3. switch to `skills/{type}/{slug}/` only after manifest/install work starts
This reduces churn while still removing hardcoded assumptions from code.
---
## 7. Writer Refactor
`tools/skill_writer.py` should move from fixed colleague wording to schema-driven rendering.
### 7.1 Current issues
- skill frontmatter name is always `colleague_{slug}`
- descriptions are colleague-specific
- identity fallback is `同事`
- output paths are externally decided, not derived from a storage policy
### 7.2 Target writer interface
Proposed create interface:
```bash
python3 skill_writer.py \
--action create \
--meta meta.json \
--work work.md \
--persona persona.md \
--base-dir ./skills
```
Writer behavior:
- read `type` and `generation.preset` from metadata
- resolve naming and storage via preset registry
- render frontmatter from schema, not hardcoded strings
- optionally emit legacy aliases for compatibility
### 7.3 Rendering rules
The writer should derive:
- frontmatter `name`
- human-readable description
- display heading
- identity line
- artifact filenames
from:
- metadata
- preset config
not from ad hoc string templates in code.
### 7.4 Compatibility mode
For `type=colleague`, preserve:
- `colleague_{slug}`
- existing filenames
- current rollback behavior
This keeps old generated skills runnable while the internals evolve.
---
## 8. Command Model
### 8.1 Current state
Primary entry is `/create-colleague`.
### 8.2 Proposed state
Introduce one canonical command plus aliases:
- `/create-skill` as canonical generic entry
- `/create-colleague` as alias with preset `colleague`
- future `/create-ex`, `/create-icon`, `/create-self` as aliases to type presets
### 8.3 Why alias-based commands are better
This gives us:
- one engine
- one schema
- one migration path
- multiple user-friendly entry points
without:
- multiple bespoke implementations
- repeated prompt definitions
- fragmented docs
### 8.4 Command resolution examples
| User command | Resolved preset | Output `type` |
|--------------|-----------------|---------------|
| `/create-skill` | chosen interactively or inferred | varies |
| `/create-colleague` | `colleague` | `colleague` |
| `/create-ex` | `relationship` | `relationship` |
| `/create-icon` | `icon` | `icon` |
---
## 9. Prompt Strategy
Prompt abstraction should happen in two levels.
### 9.1 Shared prompts
These can remain common:
- merger
- correction handler
- most of persona/work builders
### 9.2 Preset overlays
Different types likely need small differences in:
- intake questions
- identity framing
- allowed labels
- work section optionality
- persona extraction emphasis
So the prompt model should be:
- shared base prompt
- optional preset overlay or variables
not:
- fully duplicated prompt files per type
### 9.3 Example
A `character` preset may:
- downweight real-world source validation
- allow fictional world context
- treat "work skill" as capability set instead of job experience
A `self` preset may:
- prefer first-person self-description inputs
- allow private journals and notes as primary sources
These are preset-level differences, not engine-level rewrites.
---
## 10. Manifest and Packaging Direction
Roadmap Phase 3 mentions one-click install. That requires a package contract.
This phase should not fully implement install yet, but it should reserve the shape.
### 10.1 Proposed `manifest.json`
```json
{
"manifest_version": "1",
"id": "colleague.zhangsan",
"type": "colleague",
"display_name": "张三",
"entrypoints": {
"default": "SKILL.md",
"work": "work_skill.md",
"persona": "persona_skill.md"
},
"artifacts": [
"SKILL.md",
"work.md",
"persona.md",
"meta.json"
],
"capabilities": ["persona", "work"],
"install": {
"compatible_runtimes": ["claude-code"],
"min_schema_version": "2"
}
}
```
### 10.2 Why reserve it now
Because future features depend on it:
- gallery export
- download/install
- dependency checks
- multi-skill composition
If schema work ships without manifest awareness, packaging will later force another migration.
---
## 11. Collector Pluginization Alignment
This abstraction also creates a clean seam for collector plugins.
Collectors should not know about `colleague` vs `ex` vs `icon`.
They should only output normalized source material.
### 11.1 Collector contract
Collector output should be normalized into:
```json
{
"source_type": "feishu_messages",
"subject_candidates": ["张三"],
"documents": [],
"messages": [],
"attachments": [],
"metadata": {
"collected_at": "2026-04-16T00:00:00Z"
}
}
```
Then the generator decides:
- which preset applies
- how to interpret the material
- where it belongs in `work` vs `persona`
### 11.2 Benefit
This avoids building type-specific collectors such as:
- ex-specific iMessage collector
- icon-specific web article collector
at the interface level.
Instead, collectors become source adapters, while presets remain semantic adapters.
---
## 12. Gallery Impact
The website already wants category upgrades in Phase 2.
This design supports that cleanly:
- gallery reads `type`
- category label comes from `classification.gallery_category`
- tag filters come from `classification.tags`
- badges can depend on `source_context`
Without this abstraction, gallery categorization becomes fragile string heuristics.
---
## 13. Migration Plan
### Phase A: Metadata expansion
- add `schema_version`
- add `type`
- add `display_name`
- add `classification`
- add `generation`
- add `compat`
Keep all current fields that existing tooling expects.
### Phase B: Writer refactor
- refactor `skill_writer.py` to read generic metadata
- keep colleague-compatible output for `type=colleague`
- add tests for both legacy and generic metadata inputs
### Phase C: Command abstraction
- introduce `/create-skill`
- map `/create-colleague` to preset `colleague`
- keep old command as stable alias
### Phase D: Preset registry
- define preset registry file
- move hardcoded wording into preset config
- add first non-colleague preset, likely `self` or `icon`
### Phase E: Manifest introduction
- emit `manifest.json`
- keep it optional at first
- wire website/export tooling to consume it later
---
## 14. Backward Compatibility
Backward compatibility is mandatory for this phase.
### Must preserve
- existing `colleagues/{slug}` output for current users as a legacy fallback
- existing `meta.json` fields used by tools
- existing rollback/version behavior
- existing commands like `/create-colleague`
- existing generated `SKILL.md` shape for colleague skills
### Allowed changes
- adding new metadata fields
- internally routing through a preset registry
- adding `manifest.json`
- introducing `/create-skill` as a new canonical command
### Not allowed in this phase
- breaking old generated skill folders
- forcing all docs to rename overnight
- switching storage roots without compatibility mapping
---
## 15. Risks
### Risk 1: Over-generalization too early
If we design for every future case upfront, the schema becomes vague and bloated.
Mitigation:
- optimize for 3 to 5 concrete initial types
- prefer optional fields over deep inheritance
### Risk 2: Prompt complexity explosion
If each type gets a full prompt stack, maintenance cost will spike.
Mitigation:
- use shared prompts plus preset overlays
### Risk 3: Docs drift
Code may move to generic internals while docs still speak only in colleague terms.
Mitigation:
- keep external docs colleague-first until `/create-skill` is ready
- add an internal architecture doc before mass doc edits
### Risk 4: Website and repo schema divergence
If gallery metadata evolves separately from generated metadata, integration becomes brittle.
Mitigation:
- define one canonical schema source in this repo
- make website consume a mapped subset of it
---
## 16. Recommendation
The recommended next implementation order is:
1. define `schema_version=2` metadata contract
2. refactor `skill_writer.py` to render from metadata plus preset
3. introduce a preset registry with `colleague` first
4. add `/create-skill` as the canonical command and keep `/create-colleague` as alias
5. introduce `manifest.json`
6. add one additional preset to validate the abstraction
The first additional preset should be chosen for contrast.
Recommended order:
1. `self`
2. `icon`
3. `relationship`
4. `character`
`self` is the safest validation target because:
- input acquisition is simple
- privacy model is clearer than `relationship`
- it tests non-colleague identity without requiring source collector redesign
---
## 17. Open Questions
These should be resolved before implementation starts:
1. Should `type` reflect user-facing category (`icon`) or semantic domain (`public_figure`)?
2. Do we want one `profile` schema for all types, or a shared core plus type-specific extension blocks?
3. When can the legacy `colleagues/` fallback be removed now that canonical storage is `skills/{type}/`?
4. Should `manifest.json` be emitted immediately, or only once install flow begins?
5. Which non-colleague preset is the first real implementation target?
---
## 18. Short Version
The project should not add new create commands by cloning the current colleague flow.
It should first:
- define what a generic skill is
- make type a first-class metadata field
- move type-specific behavior into presets
- keep `/create-colleague` as a compatibility alias
That is the smallest change that unlocks:
- Phase 2 category expansion
- Phase 3 packaging/install
- future collector plugins
- future multi-skill orchestration
without turning the codebase into repeated type-specific forks.
-816
View File
@@ -1,816 +0,0 @@
# Skill 类型抽象设计
最后更新:2026-04-16
## 1. 背景
当前项目的核心实现,本质上还是围绕 `colleague.skill` 展开的。
这个假设已经渗透进多个层面:
- `tools/skill_writer.py` 默认生成 `name: colleague_{slug}`
- 输出目录默认是 `skills/colleague/{slug}/`
- 文档和命令默认入口是 `/create-colleague`
- `meta.json` 的语义更接近“同事档案”,而不是通用 skill 元数据
这套设计对 v1 没问题,但它会直接卡住 roadmap 里从 `colleague.skill` 演进到 `dot-skill` 的目标。
因为 roadmap 的下一阶段已经不只是“把同事蒸馏成 skill”,而是:
- 同事
- 前任 / 旧友 / 关系对象
- 偶像 / 公众人物 / 历史人物
- 自己
- 虚构角色
如果在现有结构上直接去加 `/create-ex`、`/create-icon`,结果很可能是:
- 再复制一套 intake prompt
- 再复制一套 writer 逻辑
- 到处出现 `if type == xxx`
- gallery、manifest、安装流程各自再做一遍类型分叉
这种做法短期能跑,长期一定会让代码和文档越来越难维护。
所以在真正开始做 Phase 2 之前,应该先做一层“类型抽象”。
---
## 2. 设计目标
### 目标
- 让核心生成流程支持多种 skill 类型
- 保持现有 `colleague` 工作流兼容,不打断已有用户
- 避免为每一种新入口复制一套 prompt、writer 和目录结构
- 为 gallery 分类、manifest、安装、组合编排提供统一元数据基础
- 采用渐进式迁移,而不是一次性大改
### 非目标
- 这一轮不重写全部 prompts
- 这一轮不实现 multi-skill orchestration
- 这一轮不实现 multimodal 资产体系
- 这一轮不强制把所有用户入口都改成 `/create-skill`
---
## 3. 当前问题是什么
目前系统把三件本来应该分开的事情,耦合在了一起:
1. 被蒸馏的对象是谁
2. 创建流程怎么走
3. 产物文件长什么样
在 v1 里,这三件事默认都等于“同事”。
这会带来四个直接问题。
### 3.1 对象类型被写死了
当前实现默认目标对象一定是“同事”。
这个假设体现在:
- 默认身份文案
- 生成的 skill 名称
- 输出目录命名
- 文档术语和命令名称
### 3.2 新入口会诱导出多套实现
如果不先抽象,后续一旦加:
- `/create-ex`
- `/create-icon`
- `/create-self`
最自然但最糟糕的演进方式就是:
- 每个命令各自维护 intake
- 每个命令各自处理 metadata
- 每个命令各自组装最终的 SKILL.md
这样很快就会变成多套平行实现。
### 3.3 元数据模型太窄
现在的 `meta.json` 足够支持:
- 本地生成
- 增量更新
- 回滚
但不够支持:
- gallery 分类和过滤
- install / package
- 类型化展示
- 未来多 skill 编排
### 3.4 roadmap 后续能力没有共同底座
roadmap 里后面这些目标:
- 分类升级
- 一键安装
- 持续进化
- 关系图谱
都需要一个稳定的“skill 身份模型”。
现在这个模型还不存在。
---
## 4. 核心思路
核心思路一句话:
> 不再把项目理解成“同事生成器”,而是“通用 skill 生成引擎 + 类型 preset”。
也就是把系统拆成三层:
1. `skill schema`
2. `type preset`
3. `generator pipeline`
### 4.1 Skill Schema
每个生成出来的 skill,都应该符合一套统一的元数据结构,而不是只服务于同事场景。
建议的 schema 方向如下:
```json
{
"schema_version": "2",
"id": "colleague.zhangsan",
"slug": "zhangsan",
"type": "colleague",
"subtype": null,
"display_name": "张三",
"summary": "字节 L2-1 后端工程师,直接、数据驱动",
"profile": {
"name": "张三",
"company": "ByteDance",
"level": "L2-1",
"role": "Backend Engineer",
"gender": "male",
"mbti": "INTJ"
},
"source_context": {
"domain": "work",
"relationship_to_user": "coworker",
"is_real_person": true,
"is_public_figure": false,
"is_fictional": false
},
"classification": {
"gallery_category": "Colleague",
"tags": ["backend", "direct", "data-driven"],
"language": "zh-CN"
},
"artifacts": {
"combined_skill": "SKILL.md",
"work_skill": "work_skill.md",
"persona_skill": "persona_skill.md",
"work_doc": "work.md",
"persona_doc": "persona.md"
},
"generation": {
"preset": "colleague",
"prompt_bundle": "v1-colleague",
"created_from": ["feishu", "manual_tags"],
"corrections_count": 0
},
"lifecycle": {
"status": "active",
"version": "v1",
"created_at": "2026-04-16T00:00:00Z",
"updated_at": "2026-04-16T00:00:00Z"
},
"compat": {
"legacy_command": "/create-colleague",
"legacy_storage_root": "colleagues"
}
}
```
这个结构的重点不是字段多少,而是要把以下概念分开:
- skill 是什么
- 这个 skill 属于什么类型
- 它有哪些产物
- 它怎么生成的
- 它如何兼容旧系统
### 4.2 Type Preset
类型 preset 的作用是:
在不改生成引擎的前提下,告诉系统“这一类对象应该怎么被创建、怎么被描述、怎么被展示”。
可以理解成:
- `colleague` 是一个 preset
- `self` 是一个 preset
- `icon` 是一个 preset
- `relationship` 是一个 preset
- `character` 是一个 preset
建议 preset 里至少包含这些信息:
```json
{
"name": "colleague",
"display_name": "Colleague",
"source_domain": "work",
"relationship_to_user": "coworker",
"identity_label": "同事",
"gallery_category": "Colleague",
"command_aliases": ["/create-colleague", "/create-skill"],
"prompt_bundle": {
"intake": "prompts/intake.md",
"work_analyzer": "prompts/work_analyzer.md",
"persona_analyzer": "prompts/persona_analyzer.md",
"work_builder": "prompts/work_builder.md",
"persona_builder": "prompts/persona_builder.md",
"merger": "prompts/merger.md",
"correction_handler": "prompts/correction_handler.md"
},
"writer": {
"skill_name_prefix": "colleague",
"storage_root": "skills/colleague"
}
}
```
重点是:
后续新增类型时,优先是“新增 preset”,而不是“复制一整套实现”。
### 4.3 Generator Pipeline
未来生成流程应该是:
1. 用户命令解析到某个 preset
2. 输入信息被写入统一 schema
3. 按 preset 选择 prompt bundle / overlay
4. 通用 generator 产出 `work.md` / `persona.md` / `SKILL.md`
5. writer 根据 metadata + preset 渲染最终产物
也就是:
`command -> preset -> schema -> generator -> artifacts`
而不是:
`command -> 专属实现`
---
## 5. 数据模型怎么改
### 5.1 当前模型的问题
现在的 `meta.json` 更像是:
- 一个“同事档案”
- 加上一点版本信息
它不是面向系统协作的通用元数据。
因此当前模型适合:
- writer
- rollback
- update
但不适合:
- gallery
- package / install
- future relationship graph
- multi-skill composition
### 5.2 建议拆成三层
建议把 metadata 分成三个逻辑层。
#### A. 核心身份层
- `id`
- `slug`
- `type`
- `subtype`
- `display_name`
- `summary`
#### B. 语义信息层
- `profile`
- `source_context`
- `classification`
#### C. 运行 / 生命周期层
- `artifacts`
- `generation`
- `lifecycle`
- `compat`
这样不同模块就能只读自己需要的部分:
- writer 主要读 `artifacts`、`generation`
- gallery 主要读 `classification`
- installer 主要读 `id`、`type`、`artifacts`
- orchestration 主要读 `type`、`source_context`
---
## 6. 文件结构怎么演进
### 方案 A:迁移期保留当前目录
```text
skills/colleague/{slug}/
SKILL.md
work.md
persona.md
work_skill.md
persona_skill.md
meta.json
versions/
```
优点:
- 和当前系统完全兼容
- 迁移风险最低
缺点:
- 命名仍然偏“同事”
### 方案 B:切到通用目录
```text
skills/{type}/{slug}/
SKILL.md
work.md
persona.md
work_skill.md
persona_skill.md
meta.json
manifest.json
versions/
```
优点:
- 和 dot-skill 方向一致
- 对 install / package / 分类更自然
缺点:
- 改动面更大
- 现有命令、文档、脚本都要适配
### 建议
采用两阶段迁移:
1. 把 `skills/colleague/{slug}` 作为 `type=colleague` 的规范输出目录,同时保留 `colleagues/{slug}` 的历史兼容
2. 代码内部先抽象出 `storage resolver`
3. 等 manifest / install 设计稳定后,再考虑统一切换到 `skills/{type}/{slug}`
这样改动最稳,不会一上来把 blast radius 扩太大。
---
## 7. `skill_writer.py` 应该怎么改
当前 [`tools/skill_writer.py`](../tools/skill_writer.py) 最大的问题不是“代码写得不好”,而是它承担了太多写死的 colleague 语义。
### 7.1 现状中的硬编码
- frontmatter 名称默认 `colleague_{slug}`
- 描述文案默认是“某某的工作能力 / 人物性格”
- 身份 fallback 默认是“同事”
- 输出结构默认围绕 `skills/colleague/`
### 7.2 目标接口
writer 应该从“写死文案的文件写入器”,变成“基于 schema + preset 的产物渲染器”。
理想调用方式类似:
```bash
python3 skill_writer.py \
--action create \
--meta meta.json \
--work work.md \
--persona persona.md \
--base-dir ./skills
```
writer 的职责应该变成:
- 读取 metadata
- 读取 preset 配置
- 决定命名规则和路径
- 渲染 frontmatter、identity、description
- 写出兼容的 artifacts
而不是自己决定“你一定是同事,所以名称前缀一定是 colleague”。
### 7.3 兼容要求
对 `type=colleague`,这一轮必须保持:
- `colleague_{slug}`
- 当前文件名
- 当前 rollback 行为
- 当前组合 skill 的整体结构
也就是说:
内部抽象可以变化,对外产物尽量不破。
---
## 8. 命令模型怎么设计
### 8.1 当前状态
当前核心入口是 `/create-colleague`。
### 8.2 目标状态
建议引入:
- `/create-skill` 作为通用主入口
- `/create-colleague` 作为 `colleague` preset 的兼容别名
- 后续 `/create-ex`、`/create-icon`、`/create-self` 也都是 preset alias
### 8.3 这样做的好处
这样可以实现:
- 一个生成引擎
- 一套 metadata
- 一套 writer
- 多个用户友好入口
而不是:
- 多套命令
- 多套 prompt
- 多套模板
- 多套文档解释
### 8.4 例子
| 用户命令 | 解析结果 | 输出 type |
|----------|----------|-----------|
| `/create-skill` | 用户选择或系统推断 preset | 取决于 preset |
| `/create-colleague` | `colleague` preset | `colleague` |
| `/create-ex` | `relationship` preset | `relationship` |
| `/create-icon` | `icon` preset | `icon` |
---
## 9. Prompt 应该怎么抽象
这里不建议为每个类型复制全套 prompt。
更合理的做法是“两层结构”。
### 9.1 共用基础 prompt
这些内容大概率可以共用:
- merger
- correction handler
- work/persona builder 主体结构
### 9.2 类型 overlay
不同类型真正不同的地方主要在:
- intake 提问方式
- 身份 framing
- 标签范围
- work 部分是不是必需
- persona 提取重点
因此应该优先设计成:
- 一个 base prompt
- 一层 preset overlay 或变量注入
而不是:
- 每个类型一整套完全独立 prompt 文件
### 9.3 示例
比如:
- `character` 类型可以弱化真实世界数据校验,更强调“世界观”和“角色能力”
- `self` 类型可以允许第一人称自述、日记、个人笔记作为高权重来源
- `icon` 类型可以把公开采访、演讲、文章作为主要输入源
这些都应该是 preset 差异,而不是引擎分叉。
---
## 10. 为 manifest / package 预留空间
roadmap 第三阶段里有“一键安装”,这件事一定需要一个 package contract。
这一轮不需要立刻把 install 做完,但必须先把结构想清楚,不然后面又要返工。
### 10.1 建议引入 `manifest.json`
比如:
```json
{
"manifest_version": "1",
"id": "colleague.zhangsan",
"type": "colleague",
"display_name": "张三",
"entrypoints": {
"default": "SKILL.md",
"work": "work_skill.md",
"persona": "persona_skill.md"
},
"artifacts": [
"SKILL.md",
"work.md",
"persona.md",
"meta.json"
],
"capabilities": ["persona", "work"],
"install": {
"compatible_runtimes": ["claude-code"],
"min_schema_version": "2"
}
}
```
### 10.2 为什么现在就要预留
因为后面这些能力都要依赖它:
- gallery export
- download / install
- compatibility check
- future multi-skill composition
如果 schema 升级时完全不考虑 manifest,后面大概率又会有第二次迁移。
---
## 11. 和 collector 插件化的关系
这个抽象设计其实也正好给 collector 插件化提供了边界。
collector 不应该知道“这是同事、前任、偶像还是角色”。
collector 应该只负责做一件事:
> 把外部来源转换成标准化的原始材料。
### 11.1 collector 的理想输出
例如:
```json
{
"source_type": "feishu_messages",
"subject_candidates": ["张三"],
"documents": [],
"messages": [],
"attachments": [],
"metadata": {
"collected_at": "2026-04-16T00:00:00Z"
}
}
```
之后再由 generator / preset 去决定:
- 这份材料对应哪个类型
- work 和 persona 怎么切
- 最终写到哪里
### 11.2 这样做的好处
这样可以避免未来出现:
- ex 专属 iMessage collector
- icon 专属文章 collector
- self 专属日记 collector
这种“来源逻辑”和“语义逻辑”耦合的设计。
正确做法应该是:
- collector 是 source adapter
- preset 是 semantic adapter
---
## 12. 对网站 gallery 的影响
website 在 roadmap 里已经明确要做 category upgrade。
如果主仓库先有统一 schema,这件事会很好做:
- gallery 直接读 `type`
- 分类标签直接读 `classification.gallery_category`
- filters 直接读 `classification.tags`
- 特殊徽章可以读 `source_context`
否则网站侧最后只能靠字符串规则猜:
- 名字里有没有某种词
- 描述像不像某类对象
- YAML 手填一些和主仓库脱节的字段
这会让主仓库和网站的模型越来越分裂。
---
## 13. 迁移计划
建议按下面顺序推进。
### Phase A:先扩 `meta.json`
先加:
- `schema_version`
- `type`
- `display_name`
- `classification`
- `generation`
- `compat`
同时保留所有旧字段,避免现有脚本直接挂掉。
### Phase B:重构 `skill_writer.py`
- 让 writer 从 metadata + preset 渲染产物
- 对 `type=colleague` 保持当前输出不变
- 补 legacy / generic 两种输入的测试
### Phase C:引入命令抽象
- 增加 `/create-skill`
- 把 `/create-colleague` 变成 `colleague preset` 的 alias
- 旧命令继续可用
### Phase D:引入 preset registry
- 定义 preset 注册表
- 把写死文案从代码挪到 preset 配置
- 先只落地 `colleague`
- 再加第一个非 colleague preset 验证抽象是否成立
### Phase E:引入 `manifest.json`
- writer 可选输出 `manifest.json`
- 先不强制所有下游依赖它
- 后续网站和 install 再逐步接入
---
## 14. 向后兼容要求
这一轮是架构升级,不是破坏式重构。
### 必须保持的东西
- 当前 `colleagues/{slug}` 输出结构仍然可作为历史兼容路径
- 现有 `meta.json` 被依赖的字段继续存在
- rollback / version 逻辑不破
- `/create-colleague` 不下线
- 已经生成好的 colleague skills 继续能跑
### 可以新增的东西
- 新 metadata 字段
- preset registry
- `/create-skill`
- `manifest.json`
### 这一轮不能做的事
- 直接打破旧目录结构
- 一夜之间把所有文档术语全改掉
- 强制所有老用户迁移
---
## 15. 风险
### 风险 1:过度泛化
如果一开始就想把所有未来类型都设计完整,schema 会很快膨胀得过头。
应对方式:
- 先围绕 3 到 5 种具体类型建模
- 多用可选字段,少做复杂继承体系
### 风险 2:prompt 维护成本爆炸
如果每个类型都复制一整套 prompt,维护成本会快速失控。
应对方式:
- 共享基础 prompt
- 只在 preset 层做增量 overlay
### 风险 3:代码和文档脱节
内部已经变成通用引擎,但外部文档还全部只说 colleague,容易让贡献者理解混乱。
应对方式:
- 在 `/create-skill` 真正上线前,外部文档仍然保持 colleague-first
- 先补内部架构文档,再逐步调整外部文档
### 风险 4:主仓库和网站模型分叉
如果 website 自己定义一套 category schema,主仓库自己定义一套 skill schema,后面同步会很痛苦。
应对方式:
- 先在主仓库定义 canonical schema
- 网站只消费它的一个映射子集
---
## 16. 推荐的落地顺序
建议按这个顺序实施:
1. 定义 `schema_version=2` 的 metadata 契约
2. 重构 `skill_writer.py`,让它吃 metadata + preset
3. 引入 preset registry,先只支持 `colleague`
4. 增加 `/create-skill`,但保留 `/create-colleague` 兼容别名
5. 引入 `manifest.json`
6. 增加第一个非 colleague preset 验证抽象
第一个非 colleague preset,我建议优先做:
1. `self`
2. `icon`
3. `relationship`
4. `character`
为什么 `self` 最适合最先做:
- 输入来源最简单
- 不需要先解决复杂关系伦理问题
- 不依赖新的外部 collector
- 足够验证“不是同事也能走同一引擎”
---
## 17. 需要尽快拍板的问题
正式开始实现前,建议先决定下面 5 个问题:
1. `type` 应该用用户能理解的类别名,还是更底层的语义域名?
2. `profile` 是全类型共用一套结构,还是共用核心字段 + 类型扩展字段?
3. 既然规范目录已经是 `skills/{type}/`,历史 `colleagues/` 兼容什么时候移除?
4. `manifest.json` 是现在就产出,还是等 install 流程开始时再产出?
5. 第一个非 colleague preset 选哪个?
---
## 18. 一句话总结
Phase 2 真正该先做的,不是多加几个 `/create-xxx` 命令。
而是先把这件事定义清楚:
> 一个通用 skill 到底是什么,它的类型怎么表达,它的元数据怎么组织,它的生成流程怎么复用。
只要这层抽象做好了,后面的:
- `/create-skill`
- 分类升级
- collector 插件化
- manifest / install
- multi-skill collaboration
都会顺很多。
如果这层不先做,后面每加一个类型,仓库里就会多一套复制粘贴出来的流程,最后反而把 roadmap 自己卡死。
+4 -5
View File
@@ -1,6 +1,6 @@
# Architecture
This file is the **live-tree map**. The published skill path still writes colleague-family artifacts (`work.md`, `persona.md`, `SKILL.md`), while the `distilly` product path now has a TypeScript foundation. New work must land on the design in [design/system-v3.md](design/system-v3.md), not on the legacy artifact split.
This file is the **live-tree map** for the TypeScript Distilly Plugin. The former `dot-skill` implementation remains on its own maintenance branch and is not copied into this tree. New work must land on the design in [design/system-v3.md](design/system-v3.md).
Do not implement from this page alone. Load [design/README.md](design/README.md) and the chapter that owns the change.
@@ -32,10 +32,10 @@ This section describes the current product tree. Workspace experiments outside t
- The current internal dependency edges are `@distilly/adapters → @distilly/protocol`, `@distilly/engine → @distilly/protocol`, `@distilly/runtime → @distilly/protocol + @distilly/adapters + @distilly/bindings + @distilly/engine`, `distilly → @distilly/protocol`, `@distilly/mcp → @distilly/protocol`, `@distilly/bindings → @distilly/protocol`, `@distilly/panel → @distilly/protocol + @distilly/mcp`, and outer `@distilly/cli → Protocol + Bindings + Runtime + Facade + MCP + Panel`; the Panel web graph itself reaches only Protocol. Packages have no upward or mutual edge beyond that allowlist; negative fixtures cover bare, subpath, workspace, link, file, and directory aliases.
- The MCP package's built stdio conformance child still injects a full test-only EngineClient and presenter. The CLI source integration executes the complete local data/correction/reopen graph plus Codex person-Skill install/export/uninstall and exact replay. Separately, built CLI smokes run compiled package bytes over temporary homes: one starts the private stdio Preview composition and drives the built Panel in Chromium against the same SQLite authority to promote, reject, roll back, and verify the reopened result; another uses the real binary for Codex setup, rejects deferred Claude activation, starts MCP through the installed launcher, lists exactly five tools with the official client, then uninstalls while preserving SQLite and person data. The Codex package verifier runs the actual self-contained artifact from a path containing spaces and non-ASCII characters, deletes that extraction, verifies the installed runtime through official Codex plugin/MCP listing and the official MCP client, initializes once with the host-like absence of `CODEX_HOME`, runs create/ingest/brief/commit/get/prompt/correction/Panel promote, installs a person Skill through the human CLI, proves both plugin and person-Skill discovery in fresh Codex processes without a model call, and verifies uninstall preserves SQLite and the person Skill byte-for-byte. The immutable capacity fixture was regenerated for `0.1.0-preview.1` from real Codex host events at exact 65,536-byte tool-result and complete-briefing budgets with model-observed unseen tail markers.
- No complete production Engine factory or cross-process LocalRuntime, remaining evolution services, built-in source provider, user collection service, upgrade/deep-doctor lifecycle, Claude packaged activation, or published user installation exists yet. The local `0.1.0-preview.1` Codex package is self-contained and executable but is not pushed, published, or released. Host filesystem projection precedes SQLite operation completion; a retry converges when it resolves the same Profile and destination state, while current-version drift, orphan collection, and exhaustive projection crash injection remain post-Preview hardening rather than a claimed cross-boundary atomic transaction.
- `tools/` and `prompts/` remain frozen Python serving the published skill. Tests under `tests/` cover that skill writer, installers, research helpers, and repository governance; TypeScript tests live with their source under `packages/*/src/`, with built-entry scripts under each package.
- CI on `dot-skill`, `distilly`, and `main` reports documentation and Agent Note governance once, exercises the TypeScript workspace, Protocol and source-adapter contracts, the package-boundary gate, Engine SQLite ingest/brief/lease/commit/review/rollback/correction plus retained verified-read/queue/Library fixtures, Facade/MCP/bindings/Panel tests, built stdio child conformance, real Chromium Panel behavior, and plugin release assembly on Node 22.19 and 24 across Linux and macOS, and retains Python 3.9/3.11 compile and fail-closed unittest lanes plus Ruff while the legacy skill remains.
- TypeScript tests live with their source under `packages/*/src/`; repository tests under `tests/` cover package boundaries, build artifacts, documentation generation, and Plugin assembly. Root Python scripts are repository tools, not product runtime code.
- CI on `dot-skill`, `distilly`, `distilly-plugin`, and `main` checks documentation and whitespace, exercises the TypeScript workspace, Protocol and source-adapter contracts, package boundaries, Engine SQLite paths, Facade/MCP/bindings/Panel tests, built stdio conformance, real Chromium Panel behavior, and Plugin release assembly on Node 22.19 and 24 across Linux and macOS. A small Python lane validates repository scripts and assembly.
- Generated design chapters, local Markdown links, and governed-diff Note ownership are checked by scripts under `scripts/`.
- Required-check enforcement is external GitHub state, not a fact stored in this tree. Verify it with the [branch-protection cookbook](cookbook/protecting-governed-branches.md) before claiming a red check blocks a push or merge.
- Required-check enforcement is external GitHub state; this tree only defines the checks themselves.
## What must be built
@@ -69,7 +69,6 @@ The contract is the uncut design. Entry points:
## Live data flow (today)
```
published skill: materials → tools/ + prompts/ → work.md + persona.md + SKILL.md → host skills/
product protocol: packages/protocol/src → types + runtime schemas + ESM lib entry
source/parsers: packages/adapters/src → source seams + TXT/Markdown/JSON/SRT/VTT; no providers
internal engine: packages/engine/src → SQLite mutations/raw authority + Preview verified reads; legacy regression fixtures
Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 642 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 549 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 KiB

-7
View File
@@ -1,7 +0,0 @@
# Cookbook
This folder contains executable procedures for behavior that is already shipped. Every cookbook names a real entry point and ends with a verification command that fails when the procedure is broken.
There are no Distilly product cookbooks yet because `SourceAdapter`, `HostInjector`, and `Person` are target contracts, not current APIs. Read [research/adapters](../design/v3/10-research-provenance.md), [Recall/injection](../design/v3/16-recall-and-injection.md), and the [public SDK](../design/v3/18-public-sdk.md) for their design. Add a cookbook in the same change that ships the corresponding entry point and test.
Repository administrators use [protecting-governed-branches.md](protecting-governed-branches.md) to turn CI detection into a required GitHub gate.
@@ -1,28 +0,0 @@
# Protect governed branches
The tracked hook and CI report violations, but repository files cannot prevent a direct push from landing or prevent that push from deleting the workflow itself. GitHub branch protection or a repository ruleset owns that external enforcement boundary.
## Configure
In the base repository's GitHub **Settings → Rules → Rulesets**, create an active branch ruleset targeting `dot-skill`, `distilly`, and `main`:
1. Require a pull request before merge.
2. Require every blocking CI job after a successful run exposes its exact check name: `Governance`, `Python 3.9`, `Python 3.11`, and `Ruff` for the current workflow.
3. Block force pushes and branch deletion.
4. Do not grant a broad bypass role. If administrators may bypass, record that exception as an external risk in the PR handoff.
5. Require the branch to be current before merge only if the team accepts the extra update churn; this is independent of requiring the four checks.
Apply the same policy to all three branches. Protecting only `main` does not govern the published `dot-skill` branch or the product `distilly` branch.
## Verify
Use read-only API calls against the base repository; a contributor fork is not sufficient evidence:
```sh
gh api repos/<owner>/<repo>/rulesets
gh api repos/<owner>/<repo>/branches/dot-skill/protection
gh api repos/<owner>/<repo>/branches/distilly/protection
gh api repos/<owner>/<repo>/branches/main/protection
```
Verification is successful only when the live response shows an active matching ruleset or branch protection, the four required checks, deletion and force-push denial, and the intended bypass policy. A `404`, an empty ruleset list, or checks limited to the Python matrix means governance is detection only.
+3 -5
View File
@@ -2,7 +2,7 @@
This folder is the approved target contract. It describes what the product must become; it is not evidence that an API is shipped.
[system-v3.md](system-v3.md) is the **only in-force** contract and is self-contained. [v3/](v3/) contains generated topic projections so an agent can load one section. [system-v2.md](system-v2.md), [v2/](v2/), [system-v1.md](system-v1.md), and [v1/](v1/) are deprecated history. V2 is the direct predecessor; V1 was directly superseded by V2. Do not cite either as a current requirement or rewrite their bodies to resemble V3.
[system-v3.md](system-v3.md) is the **only in-force** and self-contained contract. [v3/](v3/) contains generated topic projections for loading one section at a time. Superseded V1/V2 documents remain available in Git history but are not copied into this public Plugin branch.
Edit only a `system-v*.md` parent, then run `python3 scripts/sync_design_chapters.py`. `python3 -B scripts/verify_docs.py` fails if a generated chapter drifts. A new corpus is a `Corpus` entry in that script, never a hand-written folder.
@@ -57,8 +57,6 @@ Then load the section that owns the change.
| [28-migration-and-compatibility.md](v3/28-migration-and-compatibility.md) | Legacy migration, protocol/disk compatibility, Python retirement |
| [29-landing-and-evolution.md](v3/29-landing-and-evolution.md) | Vertical slices and release acceptance |
Changing a locked item in §3 requires a new Agent Note that names the losing alternative. Keep rationale in the Note and the resulting rule in the parent design.
Changing a locked item in §3 requires the pull request to name the rejected alternative, update the parent design, and include executable evidence for the new rule.
## Deprecated corpora
[v2/](v2/) and [v1/](v1/) retain the same generated-chapter mechanism for historical recovery. Match old and new designs by subject, not chapter number.
Historical contracts can be read from Git history. They are not current requirements or generated outputs.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18 -31
View File
@@ -2,7 +2,7 @@
> **合同状态:IN FORCE。** 本文件是当前唯一生效的目标合同;它不证明任何行为已经发布。已发布状态仍以 [architecture.md](../architecture.md)、源码与测试为准。
> **实现状态:** TypeScript 产品、MCP、插件、面板和 V3 磁盘格式尚未发布。当前 Python 技能只是迁移输入,不能当作本合同的实现。
> **与上一版的关系:** [system-v2.md](system-v2.md) 自 2026-08-20 起 deprecated。V3 保留本地优先、多主体、证据、版本与瘦门面,修复 V2 的主体创建、宿主 briefing、证据校验、双重真相、短摘要、插件 bootstrap 与面板缺口。总体理由见 [Design V3 Agent Note](../../.agents/notes/proposed/architecture/2026-08-20-design-v3.md);single-writer / SQLite authority 对旧文件事实机制的取代由 [Storage authority Agent Note](../../.agents/notes/implemented/simplification/2026-08-21-single-writer-sqlite-storage-authority.md) 独立拥有。
> **版本关系:** V3 是当前唯一生效且自包含的产品合同。它保留本地优先、多主体、证据、版本与瘦门面,并以 single-writer SQLite/WAL 加 immutable blob 取代旧文件事实机制。被取代的 V1/V2 文本仍可从 Git 历史查看,但不进入当前公开 Plugin 树。
> **章节投影:** 按章加载见 [design/README.md](README.md);只编辑本文,再运行生成器。
> 创建:2026-08-20
@@ -16,7 +16,7 @@
**准备实现一个纵向切片:** §3 确认没有重新打开锁定项 → §7 协议 → §8 五工具 → 所属机制章节 → §25 包与文件树 → §27 测试 → §29 落地验收。
**准备评审:** 先看 §3 与拥有决定的 Agent Note,再看 §4 的信任边界、§14 的 hard reject / suspended 分界、§27 的可执行证据。设计文本不是 shipped 证据。
**准备评审:** 先看 §3 的决策边界,再看 §4 的信任边界、§14 的 hard reject / suspended 分界、§27 的可执行证据。设计文本不是 shipped 证据。
### 0.2 精确词汇
@@ -190,7 +190,7 @@ distilly_get 唯一命中后,研究新材料并 ingest。新 job 的 baseVersi
### 3.1 V3 锁定项
改变以下任一项,必须新增 Agent Note,写清被打败的替代方案;不能只改代码或 generated chapter。
改变以下任一项,必须在 PR 中写清理由、被放弃的替代方案和可执行证据;不能只改代码或 generated chapter。
1. 主 UX 是 chat-first,本地面板负责可见性、证据与风险审核。
2. 面板属于首个可用版本,但 clean commit 不要求人工点击。
@@ -2146,7 +2146,7 @@ prompt version 固定为:
)
~~~
PromptCatalog 将该 promptVersion、按 raw bytes 解码的 instructions 与同一 evidenceRulesV1 放进 briefing。三者任一不匹配都是 storage_corrupt,不能只信文件名。每次变更有无 key snapshot、Agent Note(若语义改变)与旧 fixture;host-distill 历史 Version 在 creation contract 中记录使用的 promptVersion。
PromptCatalog 将该 promptVersion、按 raw bytes 解码的 instructions 与同一 evidenceRulesV1 放进 briefing。三者任一不匹配都是 storage_corrupt,不能只信文件名。每次变更都有 key snapshot 与旧 fixture;语义改变还必须在 PR 中说明理由。host-distill 历史 Version 在 creation contract 中记录使用的 promptVersion。
---
@@ -5162,25 +5162,20 @@ pnpm run gates
- system-v3.md 是唯一父合同;
- v3/ 编号章节只由 scripts/sync_design_chapters.py 生成;
- V1 / V2 保留 deprecated 历史,不改正文保持一致;
- V1 / V2 只保留在 Git 历史,不作为当前树的维护对象;
- corpus registry 在写任何文件前验证 parent、version、chapter dir、输出路径唯一和恰好一个 in-force;
- governed change 同 PR 更新 owning Agent Note;
- 合同变化与当前实现文档在同一 PR 更新;
- architecture.md 只写 shipped tree,不把 V3 目标说成已发布;
- cookbook 只在真实入口落地后写可执行步骤;
- 操作文档只在真实入口落地后写可执行步骤;
- 机器验证链接、结构、生成一致性;语义 review 判断设计是否正确。
---
## 28. Python、legacy import、存储与协议演进
### 28.1 当前 Python 遗产
### 28.1 旧产品线隔离
根 tools/、prompts/、skills/ 与 tests/ 服务已发布 dot-skill。distilly 产品分支上:
- 只接受已发布技能缺陷修复,不增加 V3 新行为;
- 根 prompts/ 冻结;V3 prompt 资产在 packages/engine/prompts;
- Python 门禁在最后一个遗产产品文件删除前保留;
- 新 TypeScript 产品不能 import 或 shell 调旧 writer 作为核心实现。
已发布 `dot-skill` 的 Python tools、prompts、生成样例与根 Skill 留在默认维护分支;它们不复制到 `distilly-plugin`。当前分支只保留仓库构建所需的 Python 脚本,V3 prompt 资产位于 `packages/engine/prompts`,TypeScript 产品不 import 或 shell 调旧 writer。未来迁移器只读取用户明确选择的旧导出或合成 fixture,不依赖当前源码树中存在旧实现。
### 28.2 LegacySkillMigrator
@@ -5242,7 +5237,7 @@ import 两阶段:
V2 TypeScript 产品、文件事实版 V3 与 `~/.distilly/` 产品格式都从未发布;没有真实用户事实、公开版本或 remote ref 依赖它们。因此首个 SQLite storage schema 从 v1 开始,不为工作区实验代码建迁移器、dual-write 或兼容读取器。旧代码只作为删除与语义对照,不作为磁盘输入。
V1/V2 文档和旧 V3 commit 保留用于理解哪些替代曾经成立,不再作为实现要求。唯一真实 legacy 输入是已发布 dot-skill 的 `work.md` / `persona.md` / `SKILL.md`;它通过 §28.2 的用户确认 import,而不是 storage migration。
V1/V2 文档和旧 V3 commit 可从 Git 历史查看,用于理解哪些替代曾经成立,不作为实现要求。唯一真实 legacy 输入是已发布 dot-skill 的 `work.md` / `persona.md` / `SKILL.md`;它通过 §28.2 的用户确认 import,而不是 storage migration。
### 28.4 独立版本维度
@@ -5276,17 +5271,9 @@ wire major 3 内允许:
产品发布后的 storage migration 只前向、显式、可 dry-run:先创建完整 backup,再在 SQLite transaction 中迁移 metadata,或在 sibling root 构造并全量验证后切换;不逐文件原地猜测升级。projection version 变化不迁移 authority,只重建。首发前的内部 schema 直接随实现替换,不积累假兼容层。
### 28.6 Python 退役条件
### 28.6 旧线退役条件
同时满足才删:
- CLI / plugin 覆盖已发布用户入口;
- migrator 对真实 legacy fixtures 全绿;
- fresh-install 与升级文档发布;
- 用户有至少一个版本周期的迁移窗口;
- dot-skill 默认分支与 distilly 产品发布策略已明确。
删除遗产时同一 change 删除对应 job、依赖、文档和冻结说明,不留永久 disabled lane。
从默认 `dot-skill` 维护线退役旧实现,仍需同时满足:CLI / Plugin 覆盖已发布用户入口;migrator 对真实 legacy fixtures 全绿;fresh-install 与升级文档发布;用户有至少一个版本周期的迁移窗口;发布策略已明确。`distilly-plugin` 当前树不复制旧实现,不等于默认维护线已经退役。
---
@@ -5294,7 +5281,7 @@ wire major 3 内允许:
### 29.1 纵向切片
已经落地的 Protocol、deterministic core、injected Facade/MCP、capability/full host bindings 与 injected Panel 保留;旧文件事实实现只是待替换代码,不再决定后续设计。迁移从以下独立 feature 重新编号,每项一份专属 Agent Note、一个本地 commit,并在接通替代路径时删除对应旧机制:
已经落地的 Protocol、deterministic core、injected Facade/MCP、capability/full host bindings 与 injected Panel 保留;旧文件事实实现不再决定后续设计。迁移从以下独立 feature 重新编号;每项使用独立分支、可审查提交与对应测试,并在接通替代路径时删除对应旧机制:
1. **Storage authority contract**:冻结单 writer、SQLite/WAL、blob、projection、doctor/backup 边界;只改合同与治理,不改产品代码。
2. **SQLite + create/ingest vertical foundation**:只建立 `subjects.create` / `materials.ingest(existing|create)` 所需的 spaces、subjects、aliases、identity hints、material metadata/blob references、current subject material membership、authoritative pending-job、operations 与 events 逻辑关系,以及一个短 write-transaction runner 和 ContentAddressedBlobStore。Blob put lease 保持到引用它的 transaction commit 或 rollback;两条方法共享 transaction-local create primitive,但 ingest(create) 不调用公开 create。该 commit 删除 live create/ingest 的 IngestTransactionRecord、staging、mutation-specific recovery、space catalog/identity locks 与旧 composition;仍被未迁移 brief/commit/review 测试使用的 shared file stores、request/subject locks 和 disposable queue 只能留在显式 test-only legacy fixture,不能被 SQLite composition import、不能 dual-write,也不是兼容路径,并由各自 owner migration 删除。没有当前消费者的 outbox、projection、doctor、backup 或 GC task abstraction 不提前出现。
@@ -5312,7 +5299,7 @@ wire major 3 内允许:
14. **Legacy import 与 fresh install**:只迁移真实 dot-skill fixtures,完成 clean install、doctor、upgrade/uninstall 与 Codex / Claude Code host reopen。
15. **其它宿主、关系、Bot、TUI、后台 executor 与 Catalog**:按真实需求分别立项,不能阻塞蒸馏主路径或扩大 Developer Preview 的宿主宣称。
前一 feature 未完成本地 commit、专属 Note、设计/standing docs 与验收时,不开始下一 feature。任何迁移 feature 都禁止 dual-write、长期 adapter 或“先保留以防万一”的未发布格式兼容层。
前一 feature 未完成可审查提交、设计/standing docs 与验收时,不开始下一 feature。任何迁移 feature 都禁止 dual-write、长期 adapter 或“先保留以防万一”的未发布格式兼容层。
### 29.2 Chat 主路径验收
@@ -5379,11 +5366,11 @@ wire major 3 内允许:
### 29.6 本文怎么演进
- 产品合同改变:先改 system-v3.md 与 owning Agent Note,再改实现。
- 产品合同改变:先改 system-v3.md 并在 PR 中记录理由,再改实现。
- 只编辑 parent;生成 v3/,门禁拒绝 drift。
- 实现落地:同 change 更新 architecture.md、tests 与必要 cookbook;不把 task progress 写进 Agent Note。
- §3.1 锁定项变化必须新 Note;§3.2 开放项关闭时写日期、结论和 owner Note。
- V1 / V2 保留历史,除状态导航外不为“保持一致”重写正文。
- 实现落地:同 change 更新 architecture.md、tests 与必要的操作文档;不把临时 task progress 写进 standing docs。
- §3.1 锁定项变化必须在 PR 中记录替代方案;§3.2 开放项关闭时写日期与结论。
- V1 / V2 只保留在 Git 历史,不为“保持一致”恢复到当前树。
- 平台能力变化优先改 HostBinding / distribution 章节;只有破坏 core contract 才升设计 major。
- 仓库外聊天、画布、未跟踪实验和模型记忆都不是规范来源。
-27
View File
@@ -1,27 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 0. v1 漏了什么(先看这个)
上一版只剩目录和函数签名。对话里已经拍板、却被收成一句的,至少有这些:
| 被收掉的 | 对话里实际说了什么 |
|---|---|
| 产品从哪来 | 你原话:Colleague Skill scope 太小,要做成所有 agent 自带的 profile;面板选 marketplace、客制化、版本自选;bot 用真人蒸出来的性格,用户 @ 交互 |
| 设计哲学 | 六条,每条有「体现」和「拒绝」。没有拒绝的原则只是口号 |
| 存储形态 | Markdown 不是记忆类项目的默认;分野是「这份存储写给谁看」。客观事实与主观判断要分开、只追加只能用在血缘上、纯文本没有 schema |
| 为什么要 SQLite | 变更检测、全文索引、向量扩展、双向同步事务。现在 persona 几 KB **不必上**;marketplace 才需要投影 |
| 队列与一致性机制 | 删 `.index/` 不丢记忆;队列表路径一行;LSN 用途与非严格单调;四道一致性守卫;写强一致读最终一致;不让不同类型记忆互相驱逐;超参数不给旋钮 |
| 蒸馏客观 vs 稳定 | 你纠正:客观 ≠ 重跑逐字相同。漂是要压的缺陷。置信度 = 材料支撑度。集合哈希没变就跳过 |
| 七组方法 | 先是完整产品能力清单(约 30 个动词),再收成 `Distilly`+`Person`。两组都要留着,不能只留瘦 SDK |
| Profile 六面语义 | 每面装什么、真实性靠什么、空合法、域包、Claim YAML、先 claim 再渲染 |
| 图 | 陈述边/派生边(后改名关系/相似)、space、pending mention、升级成 `relations/a__b/`、复杂度表、禁止 commit 时 O(n²) |
| 插件打包 | 包结构、为什么不做托管 MCP+OAuth、内嵌浏览器、装完新开对话、规范 skill symlink、问人表单的中性字段 |
| 四种调用方 | 模型 / 插件 / 面板市场 / **Bot**(后补回) |
| 三种装法 | `prompt()` 临时、`install()` 长期发现、`export()` 一对一身份。`agent.md` 不是加载机制 |
| 注入七坑 | 没有统一改 system 的 API、塞错槽污染全局、install≠会话、包装不同、子代理无 MCP、全文代价、宿主方言不能串 |
| 遥测 | opt-in、无端点完全惰性、数不到「被模型使用」、禁止为指标在 SKILL.md 里塞必调工具 |
| 现有完成度 | 目标合同和已发布代码必须分开;实时状态、测试数和 CI 以仓库证据为准,不固化在设计快照里 |
下面按对话密度写,不按「好概括」写。类和函数仍在第 9 节。
---
-33
View File
@@ -1,33 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 1. 我们现在想做什么(用你的原意,不是一句 slogan)
起点不是「做 SDK」。起点是:
Colleague Skill 只针对同事,**scope 太小**。要把它做成 **所有 Agent 自带的 profile**。别人要知道这是我们做的、会持续维护;以后所有 agent 都用这套 profile。
你当时要的产品面(2026-08-18 原话结构):
1. **前端面板与 Marketplace**
给每个 agent 建 profile。在我们自己的窗口里,从 marketplace 选一个 profile 加载进来。
2. **用户自定义**
前端里改。可以有自己的 agent、自己的「同事」这类角色。材料可以从桌面浏览记录等加载。**选择权给用户**,不由我们设采集限制(Agent 权限比普通软件大)。
3. **Evolving 与版本**
持续改人物性格,做好版本。用户自选加载哪一版。血缘必须在**版本粒度**看得见:这一百个源里有哪些。
4. **Bot**
你当时就说 **Bot 可以先做**。Bot 需要人物性格,用户 @ 交互。每个 bot 提前内置我们蒸出来的真人性格,不要 bot 自己编一份。
5. **先适配 Codex 和 Claude Code**,再铺开。要有前端面板(嵌在宿主里的窗口那种形态)。
后来定位收成一句话,但上面五条没有作废,只是分了版次:
**distilly = 用客观蒸馏,从已有事实生成可追溯的 personal memory / profile layer,再用 Agent SDK 接到 coding agent 和 bot。**
不是再做一个 Claude skill。Skill / `SKILL.md` / Hermes `SOUL.md` 都是投影。真相是引擎里的人、材料、版本、关系。
和「自带模型的个人记忆服务」的关系(纠正过一次):真正对标的是它们的 **profile 线**(单份画像、持续覆写),不是 episode 流水账。那类产品只给使用者本人做一份、覆写就没了;我们做成 **多主体、可追溯、可修正、可分享**,默认还不用单独 API key。
记谁:所有人。同事 / 关系 / 名人 / 动漫 / **self**。`self` 与他人共用同一套模型,只是 id 特殊。
材料:宿主扒网、电脑操作截飞书;用户喂文件;用户 self-correct。我们不替用户设「不准采飞书」。
---
-63
View File
@@ -1,63 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 2. 设计哲学(每条必须有拒绝)
来自完成度讨论。一条原则如果没有对应的拒绝,它就只是口号。
### 2.1 Markdown 是唯一事实来源
其余一切都是投影,且必须可从它重建。版本快照、`lineage.jsonl`、`SKILL.md` frontmatter 全部由 `meta.json` 与产物文件派生。面板与 marketplace 不得引入第二个事实来源。
**拒绝:** 把 profile 状态放进云端数据库。这是与「托管后端」形态的根本分歧——那类产品的项目状态在服务端。
### 2.2 可追溯是一等公民,不是元数据装饰
每个版本都要能回答:我是从哪些源蒸馏出来的、置信多少、覆盖多完整。provenance 进 `meta.json`,`meta.json` 进 PRIMARY_ARTIFACTS(版本快照带血缘),另有 append-only `lineage.jsonl`,快照被裁剪后血缘仍在。
**拒绝:** 只在最新版本上记血缘——回滚会让血缘与产物错位。你已确认:一百个源必须在版本粒度看得见。
### 2.3 默认惰性,显式启用
任何有副作用的能力,未显式配置时应当完全不动,包括不打扰用户。遥测没有配置端点就不问、不发。成熟度门禁默认 0.0 放行。后台 LLM 蒸馏必须显式给 key。
**拒绝:** 「默认开启 + README 教你怎么关」。受益方是我们,就不该由用户承担默认成本。
### 2.4 不为度量扭曲产品
模型读 `SKILL.md` 不执行代码,因此「persona 被使用」数不到。文档里直说遥测是**创作活跃度**,不是使用活跃度。
**拒绝:** 在 persona 的 `SKILL.md` 里塞一个必须调用的工具来凑使用量。
### 2.5 地基不压在会腐烂的接口上
越靠近宿主内部机制的东西,越不能承重。插件第一版可以不含面板:纯 MCP + 宿主原生表单。宿主的预览接口会在小版本里消失(`preview_start` 在 Claude Code 2.1.220 里已经不存在),照它写的 skill 会直接失效。
**拒绝:** 先做面板再做引擎。委托采集故意烂在宿主 skill,不烂在仓库。
### 2.6 选择权归用户
Agent 权限比普通软件大,我们不替用户设采集限制,但每个选择必须显式可见。采集器逐个 opt-in。遥测在交互式终端问一次并记住;**非交互运行一律拒绝且不落盘**,免得自动化替用户答了。
**拒绝:** 把「非交互时的拒绝」写进配置——那会让用户永远不再被问到。
### 2.7 蒸馏是客观的(你纠正之后)
从已有事实抽出结构。不是再采样一次主观印象。
我曾经把「输出是否忠于材料」和「重跑是否逐字相同」混在一起。温度计读数有波动不代表温度是主观的。
推论(比 v1 严):
- 材料没变而输出变了是 **bug**,该有测试:同一批材料重复蒸馏,结构化字段应当一致。
- **置信度 = 这条结论被材料支撑的程度**,不是模型对自己文笔的把握。材料多、直接、互相印证就高。
- **per-claim 血缘不再是开放决策**:不能溯源的判断就是模型编的。先写 claim,再渲染 Markdown。
- 新版本置信度**下降本身是异常**:材料只会增加,支撑只应变强。挂起,不是「防主观漂移」。
- 材料集合哈希没变 → **跳过蒸馏**。客观意味着幂等。在「蒸馏是主观的」框架下这个优化不成立。
- self-correct 不是「用主观覆盖主观」,是 **用户补了材料里没有的事实**。必须进 `corrections/`。修正后重蒸仍冲突 → 查提示词或解析,不是让用户反复改。
- LLM 采样噪声要**压**:降温、固定采样、结构化字段一致性校验、必要时多次采样取一致部分。不当成系统固有属性去容忍。
### 2.8 不随部署变化的东西不要给旋钮
检索算法的超参数冻成模块常量,只有部署策略才进配置文件。用户该调的是「要不要后台蒸」,不是排序融合的权重。给出不该调的旋钮是在制造事故。
---
-17
View File
@@ -1,17 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 3. 存储形态为什么是 Markdown(调研结论,不要再凭印象)
记忆类项目里用纯文本当事实层的是少数:多数走向量库、图数据库或有状态的数据库运行时。用纯文本的是另一批,主要是 coding agent 的原生上下文文件,以及少数刻意做双层的本地知识库。
分界线不是新旧,是**这份存储写给谁看**。要人打开编辑 → 纯文本;只有机器读、且记忆多到装不下 → 数据库。中间那批是刻意双层:Markdown 当事实,索引可重建。我们要的就是这个结构。产品前提是用户要在面板里改 persona,所以 Markdown 是被需求锁死的,不是偏好。
选了这条路,三个坑必须提前处理:
- **客观事实与主观判断要分开,而且要到单条粒度。** 「他叫 X」和「我认为他喜欢短回答」不能混在同一段散文里,否则后者出错时无法单独修正。只做到文件级不够;claim 把粒度对齐到单条判断。
- **只追加只能用在血缘上。** 内容哈希加只追加是保证血缘可信的正确手段,但如果把产物也做成不可变链,改一条就要重建整个文件,面板里的编辑体验直接废掉。我们只让血缘只追加,产物可改。
- **纯文本没有 schema。** agent 生态里被广泛采用的那类上下文文件恰好证明这一点:格式是共识而非规范,没有版本号,也没有约束性语言。我们做的版本化和血缘补的就是这一格。这比「又一个 memory 项目」更能说清位置。
另一类做法要求四组独立凭证全部必填(模型、向量、多模态、重排),因为它自带 LLM 调用,跟宿主模型无关。我们默认零 key,这个优势要写进 README 第一屏。
---
-44
View File
@@ -1,44 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 4. 已锁定 / 仍开放
### 4.1 已锁定
1. 独立产品与存储边界。当前产品路径在本仓 `distilly` 分支;skill 只是分发形态。最终仓库命名仍由 4.2 的开放项 F 决定。
2. Markdown / jsonl 是事实。SQLite 是派生索引 + 同步状态机,可删可重建。
3. 蒸馏客观。集合哈希没变跳过。输出漂是缺陷。
4. 默认零 key。执行者可切换:无 key 时只标 pending,宿主 `commit`;有 key 时 daemon 也走同一 `commit`。
5. 要处理多模态,**不要必填 multimodal key**。未解析成文本的不进蒸馏。
6. 采集:留 `SourceAdapter` 抽象,社区写实现。第一版主路径是宿主 `ingest`。仓库里不写飞书官方 API。
7. 接入点只有 **Recall / Capture** 两个动词。框架绑定去对钩子。不要指望模型记得调 MCP。
8. self-correct 必须变成证据:`corrections/` + 立刻新版本 + 参与下次蒸馏。置信度下降挂起,`promote`/`reject`。
9. Client 只有两个类:`Distilly` + `Person`。七组是内部模块,不是用户背的 30 个方法。但 **七组清单要留着**,否则面板一做就缺动词。
10. 一个引擎、**四张脸**:模型 MCP / 宿主插件 / 面板市场 / Bot。
11. 临时人格:父 `get`/`prompt`,完整 profile 塞进**这一次子运行**。禁止写全局 `AGENTS.md`。
12. 完整 profile 整段塞。第一版不做 salience 裁剪。适配器塞不下只报错,不准偷偷裁引擎。
13. Profile = 闭内核 + 开域 + 带证据 claim。不要顶层 `work.md`+`persona.md`。
14. `colleague`/`celebrity` 降级为默认域包。默认入口 `person`。
15. 图第一版只做**关系**(曾用名「陈述边」,已废)。不做「相似」。加节点 O(1),接关系 O(k),禁止每次 commit 全图 O(n²)。
16. Bot 是 binding,不准自建人格文件。一个 bot 第一版钉一个 subject+version。
17. 插件只装清单与 skill,不做云端 MCP 与登录。验收:不登录也能蒸公开人物并 `get`。
18. 第一版默认进程内引擎。`client.py` 先同进程门面。
19. 协议冻名字。MCP 工具名、`Material` 字段只加不改。skill 声明 `distilly >= x`。
### 4.2 开放项跟踪
| # | 问题 | 倾向 | 状态 / 关闭日期 |
|---|---|---|---|
| A | `create` 时材料必须属于谁 | 必须指明。归属推断第二版 | open |
| B | marketplace 形态 | 第二版。引擎先留 `export` / 版本标识 | open |
| C | celebrity 肖像 / 同意 | 产品政策,不进第一版引擎 | open |
| D | Bot 先 Hermes 还是 Telegram | Hermes 更便宜(已有 profile 目录);Telegram UI 现成 | open |
| E | 面板 loopback | Codex IAB 能开 127.0.0.1(已查);Claude 未实测。第一版可以无面板 | open |
| F | 仓库与包改名 | CI 已覆盖现有分支;产品命名落地时统一 `DOT_SKILL_*` → `DISTILLY_*` | open |
| G | per-claim 血缘实现粒度 | 方向已锁(必须有),落地格式见 Claim | open |
| H | `create` / `commit` 的唯一签名 | §8 能力表与 §9 public API 目前形状不同;实现前先在本文冻结一套,不做兼容重载 | open |
| I | `correct(text, facet)` 的模型边界 | 自然语言解析可用宿主模型,但证据落盘、claim 变更和版本提交必须由确定性引擎验证 | open |
| J | 无 SQLite 时 `neighbors` 的最小索引 | 第一版必须定义可重建的按 subject 关系投影,不能在查询或 commit 时扫描全图 | open |
关闭一项时,把状态写成 `closed YYYY-MM-DD`,将最终规则落到 §4.1 或所属章节,并链接同一变更中的 Agent Note;不要只删掉这一行。
---
-88
View File
@@ -1,88 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 5. 总体架构
```
绑定层 Claude / Codex / LangGraph / OpenAI Agents SDK / Hermes / Telegram
Recall = get() / prompt() Capture = ingest | accept_collect
自动路径必须挂 lifecycle hook,不能指望模型自觉
│
Client SDK Distilly + Person ← 程序员和绑定只碰这一层
│
Protocol 同进程 | 以后 JSON-RPC / MCP 共用形状
│
引擎 收集管道 → Material
蒸馏管道 → profile + claims + 关系
队列 / 版本 / 投影
│
存储 ~/.distilly/
Markdown / jsonl = 事实
.index/ = 可删可重建
```
四张脸(不是四套产品):
```
distilly_engine
/ | | \
MCP工具 Person 面板HTTP Bot binding
(模型) (脚本) (市场/图) (钉一个人)
```
分工类比:engine = 车,`Distilly`+`Person` = 方向盘。**不要把七组业务摊成 Client 上 30 个方法**,也不要把市场焊进 MCP。
蒸馏执行者:
```
无 LLM key ──► 摄入/去重/边界/排队 ──► 标 pending ──► 宿主蒸 ──► commit
有 LLM key ──► 同上 ──► daemon 蒸 ──► 同一 commit
│
置信度 ≥ 当前 → current
置信度 < 当前 → 挂起 promote/reject
```
没有 `pending` + `commit`,默认零 key 路径是断的。
多模态同一规则:图/PDF/音频先落 `raw/`;谁解析(宿主视觉 / 本机 OCR / 可选云端 key)可切换;没解析的像素不进蒸馏,否则模型会「看图编」。
三条队列,成本差三个数量级,触发必须分开:
| 队列 | 成本 | 何时 | 失败 |
|---|---|---|---|
| ingest | 廉价 | 材料落地就处理:哈希、归档、计数 | 几乎只可能是磁盘/格式,多为不可重试 |
| distill | 昂贵 | **按主体边界**,绝不逐条。无 key 只挂 pending | 限流超时可重试;材料坏了需人介入 |
| index | 中等 | 产物变了 | 派生物,失败可重建。第一版可空转 |
边界:材料积累量、时间窗、用户 `flush`。不是对话语义边界。
采纳 / 排除的存储与队列机制:
| 项 | 取舍 | 理由 |
|---|---|---|
| 三层存储 | 采纳 | Markdown 事实、SQLite 状态队列、索引可重建 |
| 队列表 + processing 守卫 | 采纳 | 认领、终态 WHERE、启动回收孤儿 |
| retryable 三态 TRUE/FALSE/NULL | 采纳 | 限流 ≠ YAML 写坏 |
| 部分索引 | 采纳 | 队列表不随历史涨 |
| 写强一致 / 读最终一致 | 采纳 | 写进文档,不留给用户撞 |
| 对外状态机比内部小 | 采纳 | 对外 pending/done/failed,processing 是内部 |
| profile 单份覆写 | 改造 | 我们有版本,current 是指针 |
| 合并式重写 + 软废弃 | 采纳方向 | 改到几十版会碎片化 |
| episode 流水账 | 排除 | 主体是人不是事件 |
| 向量召回 + 多段融合排序 | 排除 | 第一版整份进 context |
| 必填四 key | 排除 | 默认宿主蒸 |
| 采集写死在仓库 | 排除 | 主路径 agent + 用户喂 |
| 拒绝 grep 兜底 | 采纳 | 索引坏了重建,不给假可用 |
Recall / Capture 在各框架的挂载点(宿主词汇不进 SDK):
| 框架 | Recall | Capture | 注意 |
|---|---|---|---|
| OpenAI Agents SDK | `Runner.run()` 前 | 整个 run 成功后从 result | handoff 不要每个 `on_agent_end` 都提交 |
| LangGraph | `before_agent` 一次 | `after_agent` | 禁止每轮 `before_model` 都 `get` |
| AutoGen | 实现他们的 `Memory.query` | 自己包 `run()` flush | 他们不会替你 flush |
| CrewAI | `PRE_MODEL_CALL` 必须 run 级缓存 | `OUTPUT` / `EXECUTION_END` | |
| Claude / Codex | MCP + **lifecycle hook** | 结束看 `pending` | 只靠模型调 MCP 不可靠 |
Capture **不要默认把整段对话当记忆**。进库的是材料或用户修正,不是推理过程(存工具调用、排除推理内容)。Recall 默认是指定主体的 `get`,不是广搜。
---
-79
View File
@@ -1,79 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 6. 仓库文件树(源码,按角色切包)
切包的依据是角色而不是传输协议:`protocol` / `client` / `server` 三层;产品面一个文件、协议面一个文件;根只导出消费者接口。Python 落法:`api.py` / `client.py` / `models.py` / `errors.py`。
只有一个产品动词的 SDK 可以瘦到只剩一个 `run`,因为车在 runtime 里。我们的 Client 同样瘦,但不会瘦到那个程度——产品动词是「人」上的几个动作。
```
distilly/
├── README.md
├── AGENTS.md
├── pyproject.toml
├── src/
│ ├── distilly/ # 根只导出 Distilly, Person, 错误
│ │ ├── __init__.py
│ │ ├── api.py # 产品面
│ │ ├── client.py # 连进程内引擎;以后换 daemon 传输
│ │ ├── models.py
│ │ └── errors.py
│ ├── distilly_protocol/ # MCP / JSON-RPC / 以后 TS 共用形状
│ │ ├── types.py
│ │ └── mcp.py
│ ├── distilly_engine/ # 「车」。Client 不长业务
│ │ ├── store/ # layout, subject, material, index
│ │ ├── queue/ # schema, service(LSN、守卫)
│ │ ├── distill/ # hasher, runner, commit, prompts
│ │ ├── profile/ # schema, render, migrate
│ │ ├── graph/ # relations, mentions
│ │ ├── version/ # snapshot, lineage
│ │ └── project/ # SKILL.md, host export
│ ├── distilly_adapters/ # 采集。entry point: distilly.adapters
│ │ ├── base.py # 目标适配器抽象
│ │ ├── registry.py
│ │ └── builtin/ # 第一版最多一个 web 样板
│ └── distilly_bindings/ # 注入 + bot。第一版就要注入
│ ├── protocol.py # HostInjector
│ ├── claude.py
│ ├── codex.py
│ ├── langgraph.py
│ ├── openai_agents.py
│ ├── hermes.py
│ └── telegram.py
├── plugin/ # 可独立小仓。每个宿主一个包目录
│ ├── marketplace.json
│ ├── codex/
│ │ ├── .codex-plugin/plugin.json
│ │ ├── .mcp.json # python -m distilly.mcp,本地 stdio
│ │ └── skills/
│ │ ├── distilly-usage/ # 产品 skill:怎么蒸
│ │ ├── collect-web/ # 委托扒公开页
│ │ └── widget-ask/ # 仅 Codex:问「蒸哪个人」
│ └── claude/
│ ├── .claude-plugin/plugin.json
│ └── skills/ # usage/collect symlink 同一份
├── tests/
└── examples/
├── headless_ingest.py
└── spawn_ten_subagents.py
```
`plan_collect`、`promote`、适配器 registry **不准**从 `distilly` 根再 export。要深用去 `distilly.engine` / `distilly.adapters`。
各包承担的角色:
| 角色 | distilly |
|---|---|
| 共享词汇(值类型、方法名、错误码) | `distilly_protocol` |
| 产品面(用户第一眼看到的两个类) | `api.py`(`Distilly`+`Person`) |
| 协议面(可换传输的那一层) | `client.py` |
| 引擎与服务端 | `distilly_engine` |
| 扩展点 | `SourceAdapter` + entry point |
| 框架胶水 | `distilly_bindings` |
扩展点的位置很关键:**它在 engine 上,不在 Client 上。** 但 `SourceAdapter` 不是一整套插件运行时——entry point 只负责发现并实例化一次,`SourceAdapter` 只解决 Material 从哪来。
早期讨论引用过仓外 adapter、lineage 和 telemetry prototype;它们不属于本仓已发布事实。迁移只能从当前 [architecture.md](../../architecture.md)、源码和测试确认可复用能力。
---
-71
View File
@@ -1,71 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 7. 家目录文件树(运行时)
家:`~/.distilly/`,可用 `DISTILLY_ROOT` 改。
```
~/.distilly/
├── distilly.toml # 根配置(无 key 也能跑)
├── adapters.toml # 凭据;secret 字段由框架保管
├── spaces/
│ ├── entrepreneurs.china.toml # 显示名、是否虚构、默认域包
│ └── anime.naruto.toml
├── subjects/
│ └── <subject-id>/ # self / wang-xing / luffy
│ ├── manifest.json
│ ├── meta.json # schema、身份、provenance、材料集合哈希、lifecycle
│ ├── SKILL.md # 投影,可重建,不是事实
│ ├── profile/
│ │ ├── identity.md # 内核,空合法
│ │ ├── voice.md
│ │ ├── psyche.md
│ │ ├── relations.md
│ │ ├── boundaries.md
│ │ ├── texture.md
│ │ ├── timeline.md # 可空
│ │ ├── domains/
│ │ │ ├── vocation.md # 有材料才建
│ │ │ ├── craft.md
│ │ │ ├── intimacy.md
│ │ │ ├── kinship.md
│ │ │ └── public.md
│ │ └── claims.jsonl
│ ├── knowledge/
│ │ ├── messages/
│ │ ├── emails/
│ │ ├── docs/
│ │ ├── web/
│ │ ├── transcripts/
│ │ ├── raw/ # 图/PDF/音频;未转文本不进蒸馏
│ │ └── corrections/ # 最高信
│ ├── versions/
│ │ ├── vN/ # 当时整个 profile/ + SKILL + meta
│ │ └── vN-awaiting/ # 置信度下降未顶替
│ ├── lineage.jsonl
│ └── state.json # pending / awaiting_promote / 上次集合哈希
├── relations/
│ └── <a>__<b>/ # 仅「关系也值得蒸」时升级
├── graph/
│ └── relations.jsonl # 关系事实层,只追加 + valid_to
└── .index/ # 可删可重建
├── sqlite/
│ ├── queue.db # ingest/distill/index + LSN
│ └── graph.db # 节点、关系、mention;派生边以后才有
└── catalog.json # list/search 投影
```
旧 `PRIMARY_ARTIFACTS`(`SKILL.md` `work.md` `persona.md` `work_skill.md` `persona_skill.md` `manifest.json` `meta.json`)迁移时拆进内核/域。迁移器必须读取当前源码定义的 schema 版本,不能假定仓外 prototype 的 v4。`work_skill.md` / `persona_skill.md` 若还要,是 **install 时的切片**,不是家里的结构。
`install(host)`:
```
~/.distilly/subjects/<id>/ ← 唯一事实
│ install("claude-code")
▼
~/.claude/skills/<id>/SKILL.md ← 投影,可再生成
```
血缘和材料不搬家。
---
-73
View File
@@ -1,73 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 8. 七组产品能力(内部清单)+ 对外瘦 SDK
七组是**怕漏**,不是用户 API。面板 / 市场 / 批准没有这些动词会做不出来。对外第一眼仍是 `Distilly` + `Person`。
### 8.1 主体
| 方法 | 做什么 |
|---|---|
| `create(kind, name, **identity)` | 建人。kind 是域包:`person` / `colleague` / `celebrity` / `self` |
| `list(*, kind=None, space=None)` | 有哪些人 |
| `get(subject, version=None)` | 结构化 profile。Recall 用 |
| `search(query)` | 按名字 / 标签 |
| `delete(subject)` | 软删除,不物理抹血缘 |
`self` 用 `create("self")` 一次即可。
### 8.2 收集
| 方法 | 做什么 |
|---|---|
| `ingest(subject, materials)` | 所有路径汇合:落盘、哈希、去重、过边界 |
| `ingest_files(subject, paths)` | 用户丢文件 |
| `list_adapters()` | 已注册来源 |
| `resolve_subject(adapter_id, query)` | 平台上这个人是谁,多候选不猜测 |
| `plan_collect(...)` | 委托:给宿主 `AgentPlan` |
| `accept_collect(plan, artifacts)` | OCR/正文 → Material → 内部 ingest |
| `collect(...)` | Direct 适配器自己采(v1 可不实现) |
| `preflight(adapter_id)` | 凭据在不在,别浪费队列 |
宿主自己扒网收成文本,直接 `ingest`,不必经过适配器。
### 8.3 蒸馏与修正
| 方法 | 做什么 |
|---|---|
| `pending()` | 已过边界、等宿主蒸 |
| `flush(subject)` | 现在就过边界 |
| `commit(subject, draft, provenance)` | 交回结果;置信度下降 → awaiting_promote |
| `promote` / `reject` | 处理挂起版本 |
| `correct(subject, patch)` | `corrections/` + 立刻新版本 |
| `status(subject)` | 材料数、集合哈希、队列态、confidence / maturity |
### 8.4 版本
| 方法 | 做什么 |
|---|---|
| `versions(subject)` | 列表 + 每版 provenance 摘要 |
| `diff(subject, a, b)` | 两版差异 |
| `rollback(subject, version)` | 恢复为当前;血缘记一次 rollback,不删历史 |
| `lineage(subject, version=None)` | 读 jsonl,版本粒度源清单 |
### 8.5 装载
| 方法 | 做什么 |
|---|---|
| `prompt(subject, version=None)` | 只给模型看的投影字符串,不落宿主目录。临时 10 个用这个 |
| `export(subject, host)` | 一对一身份文件(SOUL.md / agent.md) |
| `install(subject, host)` | 写入该宿主 skills 根。实现必须是 host id → 安装器插件,不能写死 Claude 路径 |
| `uninstall(subject, host)` | 去掉投影,不动家里 |
### 8.6 市场(接口留着,实现第二版)
`browse` / `pull` / `publish`。不要进 MCP,不要进 README 第一屏。
### 8.7 关系
`link` `invalidate` `neighbors` `path` `subgraph` `mentions` `resolve_mention`
`similar` / `rebuild_graph` 留给以后的「相似」,第一版不做。
---
-277
View File
@@ -1,277 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 9. 类与函数(实现规格)
### 9.1 `Distilly`(`src/distilly/api.py`)
```python
class Distilly:
def __init__(self, root: str | Path = "~/.distilly", *, client: EngineClient | None = None) -> None: ...
def person(self, subject_id: str, *, space: str | None = None) -> Person: ...
def create(self, subject_id: str, *, space: str, display_name: str,
domain_pack: str = "person", aliases: list[str] | None = None) -> Person: ...
def list(self, *, space: str | None = None) -> list[SubjectSummary]: ...
def search(self, query: str) -> list[SubjectSummary]: ...
def pending(self, *, subject_id: str | None = None) -> list[PendingJob]: ...
def commit(self, job_id: JobId, draft: DistillDraft, *,
actor: Literal["host", "daemon", "user"] = "host") -> Version: ...
def promote(self, version_id: VersionId) -> Version: ...
def reject(self, version_id: VersionId, *, reason: str | None = None) -> None: ...
def subgraph(self, seed: list[SubjectId], *, hops: int = 1) -> RelationGraph: ...
def close(self) -> None: ...
```
市场方法第二版再挂,不要为了做市场养肥 `Person`。
### 9.2 `Person`
```python
class Person:
@property
def id(self) -> SubjectId: ...
@property
def space(self) -> SpaceId: ...
def get(self, *, version: VersionId | None = None) -> Profile: ...
def prompt(self, *, version: VersionId | None = None) -> str:
"""完整中性 Markdown。第一版 = render(get()),不裁。"""
def ingest(self, materials: list[MaterialIn], *, source: str) -> IngestResult: ...
def ingest_files(self, paths: list[Path], *, kind: str = "docs") -> IngestResult: ...
def accept_collect(self, plan: AgentPlan, artifacts: list[str], *, adapter_id: str) -> IngestResult: ...
def collect(self, adapter_id: str, request: CollectRequest) -> AgentPlan | IngestResult:
"""委托返回 Plan;Direct 自己采。"""
def correct(self, text: str, *, facet: str | None = None) -> Version: ...
def flush(self) -> PendingJob: ...
def status(self) -> SubjectStatus: ...
def versions(self) -> list[Version]: ...
def diff(self, a: VersionId, b: VersionId) -> ProfileDiff: ...
def rollback(self, version: VersionId) -> Version: ...
def lineage(self, *, version: VersionId | None = None) -> list[LineageEvent]: ...
def install(self, host: HostName) -> InstallRef: ...
def uninstall(self, host: HostName) -> None: ...
def export(self, host: HostName, dest: Path) -> Path: ...
def link(self, other: str | Person, *, type: str, evidence: list[EvidenceRef],
confidence: float | None = None) -> Relation: ...
def invalidate(self, relation_id: RelationId, *, reason: str) -> None: ...
def neighbors(self, *, type: str | None = None) -> list[Relation]: ...
def path(self, other: str | Person, *, max_hops: int = 3) -> list[Relation]: ...
def mentions(self) -> list[PendingMention]: ...
def resolve_mention(self, mention_id: MentionId, subject_id: SubjectId) -> Relation: ...
```
README 第一屏只写:`get` `ingest` `ingest_files` `correct` `install` `link` `neighbors`,外加 `Distilly.pending/commit`。其余是句柄上的次要方法。
和「一个 `add` 就地抽事实」的做法差别必须保留:我们 `ingest` 只收材料,`commit` 才是蒸完的人。两个动词不能合成一个,否则零 key 断了。
### 9.3 `EngineClient`(`client.py`)
```python
class EngineClient(Protocol):
def call(self, method: str, params: dict[str, Any]) -> Any: ...
def close(self) -> None: ...
class InProcessEngineClient:
def __init__(self, root: Path) -> None: ...
class DaemonEngineClient:
"""第二版。stdio / UDS JSON-RPC。方法名与 InProcess 相同。"""
```
方法名与 protocol 对齐:`subjects.*` `materials.ingest` `distill.*` `profile.*` `graph.*` `hosts.*`
### 9.4 值类型(`models.py`)
```python
SubjectId = Branded[str, "SubjectId"]
# VersionId JobId RelationId MentionId SpaceId 同理
HostName = Literal["claude-code", "codex", "langgraph", "openai-agents", "hermes", "telegram"]
@dataclass(frozen=True)
class EvidenceRef:
material_digest: str # 目标格式:src_ + 8 位 hex
quote: str | None = None
path: str | None = None
@dataclass(frozen=True)
class Claim:
id: str
facet: str # voice.opener / texture.hands / psyche.contradiction.thrift-vs-gift
text: str
evidence: tuple[EvidenceRef, ...]
confidence: float # 材料支撑度 0..1
salience: float # 第一版写入,暂不裁剪
domain: str | None = None
observed_in: tuple[str, ...] = ()
valid_from: datetime | None = None
valid_to: datetime | None = None
@dataclass(frozen=True)
class CoreFacet:
name: Literal["identity", "voice", "psyche", "relations", "boundaries", "texture", "timeline"]
markdown: str
@dataclass(frozen=True)
class DomainFacet:
name: str
markdown: str
@dataclass(frozen=True)
class Profile:
subject_id: SubjectId
version_id: VersionId
core: tuple[CoreFacet, ...]
domains: tuple[DomainFacet, ...]
claims: tuple[Claim, ...]
confidence: float
maturity: Literal["sparse", "forming", "stable"]
rendered: str
@dataclass(frozen=True)
class MaterialIn:
kind: str # message / document / web / transcript / ...
content: str # 进蒸馏必须是文本
source_id: str | None = None
occurred_at: datetime | None = None
participants: tuple[str, ...] = ()
metadata: dict[str, object] = field(default_factory=dict)
@dataclass(frozen=True)
class DistillDraft:
material_set_hash: str
claims: tuple[Claim, ...]
core_markdown: dict[str, str]
domain_markdown: dict[str, str]
relations: tuple[RelationDraft, ...] = () # commit 可附带抽到的关系,否则 1000 人蒸完图是空的
notes: str | None = None
@dataclass(frozen=True)
class Version:
id: VersionId
subject_id: SubjectId
parent_id: VersionId | None
actor: Literal["host", "daemon", "user"]
material_set_hash: str
confidence: float
status: Literal["current", "suspended", "rejected", "historical"]
@dataclass(frozen=True)
class Relation:
id: RelationId
space: SpaceId
a: SubjectId
b: SubjectId
type: str # 开放点分:work.invested / canon.rival / fanon.*
role: dict[str, str] | None # {src: "invested", dst: "founded"}
evidence: tuple[EvidenceRef, ...]
confidence: float
valid_from: datetime
valid_to: datetime | None
extracted_from: VersionId | None
@dataclass(frozen=True)
class PendingMention:
id: MentionId
raw_name: str
context: str
subject_hint: SubjectId | None
```
Claim 落盘示例(对话里的形状,实现时对齐):
```yaml
id: clm_8f3a
facet: voice.opener
statement: "语音开场几乎总是『喂——你听得到吗』,从不用『在吗』"
salience: high
confidence: 0.86
evidence: [src_a1b2, src_c3d4]
domain: null
observed_in: ["voice-note", "late-night"]
```
### 9.5 错误
`DistillyError` `NotFound` `AlreadyExists` `StaleVersion` `PendingCommit` `ConfidenceGate` `AmbiguousMention` `HostUnsupported`
采集错误用适配器那棵树,`ingest` 再映射。
### 9.6 引擎关键类
```python
class Layout: # 全部路径约定
def subject_dir / profile_dir / core_md / domain_md / claims
def knowledge / corrections / versions / lineage / relations_log / queue_db
class MaterialStore:
def put(self, subject, item) -> tuple[str, bool]: ... # digest, is_new
def inventory(self, subject) -> tuple[str, ...]: ... # raw 未转文本的不在内
def set_hash(self, subject) -> str: ...
class SubjectStore:
def create / get / list / read_profile / write_current
class QueueService:
def enqueue(self, kind: Literal["ingest","distill","index"], subject, payload) -> JobId: ...
def claim(self, kind, worker) -> QueueRow | None:
"""UPDATE ... WHERE status='pending'。rowcount==0 表示被抢。"""
def finish(self, job, *, ok: bool, retryable: bool | None, error: str | None) -> None:
"""WHERE status='processing'。用户又改文件导致已 UPSERT 成 pending 时,丢掉过时的 done。"""
def recover_orphans(self) -> None:
"""启动:processing → pending。"""
def pending_distill(self, subject=None) -> list[PendingJob]: ...
class MaterialHasher:
def hash_set(self, digests: Sequence[str]) -> str: ...
class DistillRunner:
def should_run(self, subject) -> bool: ... # 哈希相同 → False
def host_briefing(self, subject) -> HostBriefing: ...
def run_llm(self, subject, config: LlmConfig) -> DistillDraft: ...
class DraftValidator:
def validate(self, draft, expected_hash) -> None: ... # 空核合法;claim.facet 语法
class CommitService:
def commit(self, job, draft, actor) -> Version: ...
def promote / reject
class ProfileRenderer:
def render_facet(self, facet, claims) -> str: ...
def render_prompt(self, profile) -> str: ... # 第一版不按 salience 丢
class V4Migrator:
def migrate_subject(self, old_dir, dest) -> None: ...
class RelationLog:
def link / invalidate / neighbors # neighbors 必须走部分索引,禁止热路径全文件扫
class MentionQueue:
def add / resolve
class SkillProjector / HostExport
```
队列表一个主体(或一个路径)一行,不是一个事件一行——worker 来不及处理时同一主体被改十次,UPSERT 成最新,天然去重。LSN:给顺序、重新入队公平、算积压。注意 **`MAX(lsn)+1` 不是严格单调**,两个并发 writer 可能撞号;第一版接受这个精度,以后做变更流再 `BEGIN IMMEDIATE`。mtime 容差必须和对账器共用一个常量。
失败三态:`retryable=True` 自动再入队;`False` 等人改文件(改了会变 mtime);`NULL` 这行没失败。内容变了重试计数清零。
### 9.7 MCP(模型那张脸)
只这些。不要把七组都变成 tool。`link` 第二版再给模型,避免乱连。`browse` 永远不要给模型当常用工具。
| 工具 | 对应 |
|---|---|
| `distilly_get` | `Person.get` / `prompt` |
| `distilly_ingest` | `Person.ingest`(必须带 subject_id) |
| `distilly_pending` | `Distilly.pending` |
| `distilly_commit` | `Distilly.commit` |
| `distilly_correct` | `Person.correct` |
1000 个人不能 `get` 一遍;模型只应对当前这个人 `get`。
---
-36
View File
@@ -1,36 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 10. 采集适配器(目标设计)
目标文件:`src/distilly_adapters/base.py`。
三种 mode:`direct_api` / `direct_browser` / `agent_delegated`。只许子类 `DirectAdapter` 或 `DelegatedAdapter`。构造无网络、无读凭据。适配器写盘即越权。
主路径第一版:**模型采完 `ingest`**。适配器是降摩擦,不是开关。没有飞书适配器,蒸馏照样能跑。`direct_api` 第一版只留接口,仓库里不写飞书官方 API。最多带 1~2 个委托样板(`web`、`feishu`)证明社区能扩。
材料类型留在抽象里,不绑厂商视觉 API:`text` `image`(附可选 OCR)`document` `audio`(附可选转写)。
错误:`AdapterError`(`retryable` + `remediation`)、`AdapterAuthError`、`AdapterScopeError`、`AdapterUnavailable`、`AdapterRateLimited`(`retry_after_seconds`)、`AdapterTransient`。
值类型:`SubjectRef` `Material` `AdapterCapabilities` `CollectRequest` `PreflightResult` `AgentPlan`。
```python
class SourceAdapter(ABC):
adapter_id: str
display_name: str
def capabilities(self) -> AdapterCapabilities: ...
def config_fields(self) -> dict[str, str]: ... # _token/_secret/_key 当秘密
def preflight(self, config) -> PreflightResult: ...
def resolve_subject(self, query, config) -> list[SubjectRef]: ...
class DirectAdapter(SourceAdapter):
def collect(self, subject, request, config) -> Iterator[Material]: ... # 生成器,部分成功先 yield 再 raise
class DelegatedAdapter(SourceAdapter):
def plan(self, subject, request) -> AgentPlan: ...
def accept(self, plan, artifacts) -> Iterator[Material]: ... # 解析失败用 AdapterUnavailable,不可重试
```
注册表:`ADAPTER_ENTRY_POINT_GROUP = "distilly.adapters"`;`register` / `load_adapters` / `get_adapter`。第三方 import 失败:警告并跳过。
---
-65
View File
@@ -1,65 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 11. 宿主注入适配器(第一版就要)+ 实际会碰到的坑
采集适配器可以后做。**注入适配器第一版就要有**,否则 `get` 在各家会塞错地方。
它和「问人表单」(§13)是同一道缝的两个方向:中性语义按宿主翻译。那边翻译「问人」,这边翻译「灌人格」。
```python
@dataclass(frozen=True)
class Injection:
instructions: str
subject_id: SubjectId
version_id: VersionId
display_name: str
class HostInjector(Protocol):
host: HostName
def inject_subagent(self, injection: Injection, request: HostSpawnRequest) -> HostSpawnRequest:
"""禁止写全局 md。禁止把这次注入登记成 install。"""
def install(self, profile: Profile, dest_root: Path) -> InstallRef: ...
def export_identity(self, profile: Profile, dest: Path) -> Path: ...
```
`get` 只产出一份中性 Markdown。不要为 Claude 和 Codex 蒸两份 profile。各适配器只加前后几句包装(「你就是下面这个人」/ Hermes 第二人称)。
三种装法,混用会把产品做脏:
```
profile/(家里,唯一事实)
├─ prompt() / get() → 这一次子代理 ← 临时 10 个
├─ install(host) → 宿主 skills/ ← 长期、可发现
└─ export(host) → agent.md/SOUL.md ← 一个常驻身份一个文件
```
`agent.md` / `AGENTS.md` / `CLAUDE.md` / `SOUL.md` 都是**这份运行时的全局说明书**。一份进程通常只吃一套,用来写「怎么测试」,不是用来轮换人格。
- 改全局文件 = 所有对话、所有子代理一起变
- 派 10 个临时的还要写 10 份、用完再删,和宿主缓存缠在一起
- 10 个人写进同一份,上下文又挤又串台
所以:`agent.md` 只适合「这一个常驻 bot 长期就是王兴」。不适合「现在并行 10 个企业家」。`install` 也偏长期,且常要新开对话(工具/技能按会话固定)。
**「会话级」在 coding agent 里 = 子运行级注入,不是改当前窗的隐藏 system。** 各家都没有稳妥的「给当前会话打补丁」API。
| 环境 | 实际口子 | 10 个临时 |
|---|---|---|
| Claude Code | 派 Task / 子代理时自定义 prompt | 10 次派发,每次换一个人 |
| Codex | 子任务 instructions / Runner dynamic instructions | 同上 |
| OpenAI Agents / LangGraph | 每轮 run 的 instructions | 最干净 |
| Hermes / Telegram | 一个进程一份人格 | 不适合「临时」;要 10 个就 10 个进程 |
你会碰到的七件事(适配器必须挡住):
1. **没有统一的「设置系统提示」。** 父对话里 `get` 了,父自己不会自动变成那个人。10 个临时必须派 10 个子运行。
2. **塞错槽位污染全局。** 写成改 `AGENTS.md`/`CLAUDE.md` = 全仓库沾上,10 个人互相覆盖。产品 skill 第一禁令。
3. **`install` ≠ 会话注入。** 适配器不要默认走 install。
4. **各家包装不同。** 中性正文一份。
5. **子代理不一定带得上 MCP。** 人格必须已经在它的 prompt 里。父 get、子只拿文本。
6. **完整 profile 的代价。** 10 路 = 10 份全文。第一版不管裁剪;塞不下只报「塞不下」。
7. **不要跨宿主调 UI。** 不要在一家里用另一家的征询 HTML,也不要反向调用它的卡片接口。只调用本宿主真有的 spawn/instructions API。
产品 skill 写死:先 `get`,再按当前宿主适配器投放,禁止改仓库里的 `AGENTS.md`。父模型必须记得调 `get`,或写成固定流程——只靠模型自觉不可靠。
---
-27
View File
@@ -1,27 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 12. 插件怎么做(本机引擎,不做云端)
```
codex/.codex-plugin/plugin.json + .mcp.json + skills/
claude/.claude-plugin/plugin.json + skills/ # 规范 skill symlink
```
用户侧路径:加一次 marketplace 仓库 → `plugin add` → **必须新开对话**(宿主在会话启动时读工具清单)。表单走宿主原生能力。要给用户看编辑器时返回一个 URL 让宿主内嵌浏览器打开,**模型不能去点编辑器 DOM**。
该做:一个 git 当 marketplace;`codex/`+`claude/` 两包;规范 skill symlink;`plugin.json` 里填满宿主的展示位;装完提醒新开对话;表单走宿主原生能力。
不该做:远程 MCP + 登录换 token(等于项目状态在云上);人的数据放云;第一版就做内嵌大面板;在插件包里复制引擎。**我们的内容不需要加载到云端**,profile 只在 `~/.distilly`。
验收(Codex 插件成立的四条):
1. `plugin add` 之后新开对话,模型能列出五个工具
2. 「蒸馏公开人物 X」走完:浏览 → ingest → commit → 本地出现 `subjects/`
3. 下一句「你是 X」能 `get` 到声音和例句
4. **不登录任何云账号**也能完成
面板以后:MCP 返回 `http://127.0.0.1:<固定端口>`,Codex IAB 能开 loopback(已查)。Claude 侧可行性未验证。第一版可以没有面板。
`install` 实现必须是安装器插件,每多一个宿主加一个安装器,和适配器同一道缝。
---
-22
View File
@@ -1,22 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 13. 问人表单是什么
不是一种表单控件,是一份「问人」的适配 skill。不决定问什么,只决定:在这个宿主上,结构化问题用什么原生 UI 问出来。
网页能渲染的富控件,Codex/Claude 渲染不了。所以先抽中性字段再翻译:
| 语义类型 | 意思 |
|---|---|
| `short_text` | 一行文本 |
| `explicit_consent` | 必须用户主动确认,不能预勾 |
| `playable_single_choice` | 单选,选项上能带试听 |
| `playable_preview` | 只展示,不提交 |
| `audio_reference` | 表单里不能上传;让用户把文件附到对话上 |
- Codex:`ask_followup_questions`,MCP App 卡片。不要输出 HTML。
- Claude:Elicitation / `show_widget`。**禁止**调 `ask_followup_questions`。
distilly 若第一版要在 Codex 里问「蒸哪个人 / 选哪个版本」,才需要这一层。只做 get/ingest/commit、用对话能问清楚的,可以先不做。判据是**选项集是不是封闭且带媒体**——一旦要问「从这十段语音里选一段」,纯对话一定会问乱。
---
-33
View File
@@ -1,33 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 14. Bot(你早就说可以先做,中间被挤掉了)
Bot 不是第四套引擎,是 **Person 的又一种装法**:常驻对话入口,默认 `get` 某个人,用户 @ 跟这个人说话。
| | Codex / Claude 插件 | Bot |
|---|---|---|
| 谁在跑 | 用户打开的 coding agent | 挂着人格的对话进程 |
| UI | IDE + MCP + 可选面板 | Telegram / Discord / Hermes |
| 一次加载 | 可以 `get` 不同人 | 通常钉死一个人 |
| 采集 | 模型去扒 | 用户丢消息/图/语音 → `ingest` |
Hermes profile = 独立 agent(SOUL.md、技能、头像)。那是我们的 `export`/`install` 目标,不是另一种 profile。
Telegram:启动时 `person.get()` 塞进 system,每轮用户消息 `ingest`(或先缓冲)。**聊天窗口就是面板。**
版本:第一版一个 bot 钉一个 subject + 一个 version。要换人就换一个 bot。图在家里,bot 只是图上某一个节点的嘴。
Bot 24 小时自己回,需要的是 **bot 宿主的对话模型 key**,不是 distilly 的蒸馏 key。蒸馏仍可:人在 Codex 里蒸好再 `install`;或 bot 看到 `pending` 再蒸(走有 key 那条执行者)。
**不准自己实现一套人格文件。** 只准 `get`/`ingest`/`commit`/`install`。bot 只是多一个 binding,不要再复制一份 `profile/`。
建议落地(补回你早先的优先级):
1. 引擎 + `Person` 五个动词
2. **一个 bot 绑定**(Hermes 或 Telegram)
3. Codex / Claude 产品插件
4. 面板 / 关系图 / 我们自己的市场
插件让 coding agent **做**蒸馏;bot 让普通人 **跟蒸好的人说话**。
---
-54
View File
@@ -1,54 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 15. Profile layer(为什么 work+persona 装不下所有人)
顶层 `work.md`+`persona.md` 把「人」默认切成「同事」。关系、名人只能再各写一套 Layer,每多一类人就分叉一次。佳秀那份例子写得好,是因为口头禅、星座、哈哈哈这些细节在;结构上她仍然先是「HRBP」。换成母亲、主播、已故作家、你自己,`work.md` 要么空、要么硬编职责范围。
正确:所有人共用同一套内核;差异进可选的域;细微性格进带证据的 Claim。
### 15.1 内核(闭集,改要 bump schema)
| 面 | 文件 | 装什么 | 真实性主要靠它 |
|---|---|---|---|
| 身份 | `identity.md` | 名字、别称、**复数角色**(可以同时是妈妈、编辑、前同事)、公开/私下身份 | 角色是列表,不是一个 job title |
| 声音 | `voice.md` | 口头禅、节奏、标点、**会怎么说的对话例** | **最重要**。没有例就没有这个人 |
| 内在 | `psyche.md` | 价值观排序、矛盾(人可以自相矛盾)、怎么决定、怎么回避 | 比 MBTI 标签真 |
| 对人 | `relations.md` | 对不同关系的模式:亲密、陌生、权威、群体。用关系类型,不用职级 | 同事只是其中一种 |
| 边界 | `boundaries.md` | 雷区、拒绝方式、不会做的事 | 没有边界的人像角色扮演 |
| 质地 | `texture.md` | 身体习惯、口味、时间感、具体物件、只有 ta 会做的小事 | 「越真实越好」主要加在这里 |
| 时间 | `timeline.md` | 只记有证据的时间点 | 可空 |
内核**禁止**出现「职责范围 / 技术栈 / 对上级汇报」。那些属于域。
蒸馏规则:**材料撑不住的面就空着或标 unassessed,不许用模板句填满。** 空是合法状态。客观蒸馏在文件树上的含义就是这个。
### 15.2 Domain 和 vocation(你问过「vocation 是什么」)
**Domain** 是这个人生活里的一块,有材料才建,没有就不建。不是标签,也不是人的类型。
**Vocation** 是其中一块:ta 怎么做事、靠什么立足——上班、创业、做研究、当老师都算。故意避开「工作 / work」:work 太像同事 skill 里那份职责档案。全职父母、学生、僧人、职业棋手可能有 vocation,母亲往往**没有这个文件**。
| | 内核 | vocation 域 |
|---|---|---|
| 企业家 | 说话先否定再给方案 | 怎么看项目、怎么待投资人、哪几次出手 |
| 动漫角色 | 口头禅、冲动、对谁护短 | 若「职业」是忍者/猎人才写 |
| 你妈妈 | 催你吃饭的句式 | 往往没有 |
| 同事佳秀 | 星座、哈哈哈、语音习惯 | HR 的 pipeline、面评、HC |
还可以有:`craft`(创作方法)、`intimacy`、`kinship`、`public`、`civic`、以后 `fandom.md`——加域不加引擎。
域包:`create("colleague")` = 建议启用 vocation;`create("person")` = 只蒸内核。这是默认打开哪些域,不是两种人型。
### 15.3 Claim
「说话前先笑一下」「拒绝时会先夸对方」「只在语音里骂人」不该再塞进某 Layer 散文里无法溯源。
`facet` 开放点分命名,新细节 = 新路径,不 bump schema。内核 Markdown 给人读、给模型演;`claims.jsonl` 是机器索引和血缘。蒸馏时**先写 claim,再渲染各面散文**。
置信度按「有证据的 claim 覆盖了多少内核面」算,不是模型对自己文笔的打分。
`correct`:`corrections/` 一条材料 → 立刻改对应 claim + 重渲染那一面。
可扩展性靠:内核闭、域开;facet 走数据不走发版;空面合法;家族降级成域包;投影与事实分离(以后给 bot 短卡、给面板全卡,都从同一棵 `profile/` 编)。
---
-48
View File
@@ -1,48 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 16. 关系图
1000 人不能靠再堆 `relations/a__b/` 目录。那种目录是「这段关系本身也值得蒸一版 profile」。图谱要的是大量轻边。
### 16.1 两种边(先讲清楚,再只用第一种)
曾用名「陈述边 / 派生边」,你不喜欢「陈述」。对用户和面板:
| | 关系 `Relation`(第一版做) | 相似 `Affinity`(第一版不做) |
|---|---|---|
| 从哪来 | 材料写明、蒸馏抽到、或手动 `link` | 两人在同一 facet 上足够像 |
| 例子 | 合伙、师徒、对手、夫妻、同部作品 | 都用「不是,我的意思是」开场 |
| 存哪 | 事实层 `graph/relations.jsonl`,进血缘 | `.index/`,删了能从 claim 重建 |
| 变了怎么办 | `valid_to` 失效,不删 | claim 变了重算 |
不要把「我觉得他们性格像」写成关系——没证据会把图弄脏。官网上写了是联合创始人,不要只当相似度。
面板以后:关系实线,相似虚线。点实线看证据和时间;点虚线看对上的那几条 claim。
### 16.2 节点、space、边字段
节点就是已有 `subject`。企业家、动漫角色都是 `person`,差在域和 **space**,不新造 node 类型。1000 个企业家和 1000 个火影角色默认各一个 space。边默认不出空间;「这个角色像哪个企业家」是跨空间查询,显式打开。
边字段:`type` 开放点分,与 `claim.facet` 同一套扩展法。先给常用:`family.*` `intimacy.*` `work.coworker` `work.founded` `work.invested` `work.rivals`;叙事 `canon.ally` `canon.rival` `canon.mentor` `fanon.*`(同人必须标 fanon);观念 `influenced_by` `opposed_to`。同一对节点可以有多条不同类型。
边多到「这段关系本身要蒸」时,再升级成 `relations/{a}__{b}/` 完整树。1000 人里只有少数对需要升级。
`commit` 必须能带 `edges[]` / `relations`,否则批量蒸完图是空的。对不上的名字进 `pending_mentions`,人点一下对齐——自动猜错会把图污染到无法用。
动漫角色打开 `fictional`。边可以标 canon/fanon。
### 16.3 复杂度(你问过是不是 O(n))
| 动作 | 复杂度 |
|---|---|
| 插入节点 | **O(1)**。建目录、插一行索引 |
| 接上关系 | **O(k)**,k = 这次抽到的关系数,与全图人数无关 |
| 相似(倒排,以后) | O(C × 该 facet 平均人数),通常 ≪ O(n) |
| 相似(朴素) | O(n)。1000 人可接受,10 万不行 |
| 整库 rebuild | O(人数 + 边 + 全部 claim) |
**不要在每次 commit 里对全图做两两全比较(O(n²))。** 那才会炸。
宽 facet(「都爱用句号」)以后也不物化相似边,只在查询时算。
---
-15
View File
@@ -1,15 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 17. 为什么现在可以不上 SQLite 检索,以后怎么上
索引分三组表:文件系统镜像(`checksum`/`mtime`/`size` 跳过未变文件;永久标识与当前路径分离,引用不因搬家失效;关系表「指向谁」可空、「指向的名字」必有 = 前向引用);同步状态机(库与磁盘各一份版本+校验和,含「检测到外部改动」状态;区分移动留下的空位和真复制);全文索引(把 `/` 加进分词字符集让路径不被切碎,前缀索引长度按查询习惯配)。
**现在不需要。** 一份 persona 几 KB,整份进 context。引入是过度工程。
**marketplace 一做就会需要。** 面板要在几百个 profile 里按家族、成熟度、血缘来源筛;`lineage.jsonl` 问「第 5 版用了哪些源」得全文件扫。到那时:Markdown/JSONL 仍是事实,SQLite 只做可删重建的投影。目标材料 ID 使用 `src_` + 八位 hex;实现还必须提供可测试的增量重建判据。
embedding 大概率不需要。只有市场上千个 profile 要语义搜时,也可以跑本地模型,仍不必 key。
读路径规则(备忘,第一版不用):检索组件只读;按类型**硬分区**不是查一张大表再 where 过滤;组件缺失直接抛错,不退化成 grep;不要让不同类型的记忆在同一排序空间里竞争,否则一条八卦会顶掉一条工作事实。我们三个家族以后若检索,与其过滤,不如从一开始走不同召回——celebrity 涉及公众人物,本该和同事走不同策略。
---
-14
View File
@@ -1,14 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 18. 遥测(你问过能不能记次数上传服务器)
遥测是目标能力,不是当前仓库已发布模块。早期讨论曾用仓外 prototype 验证 opt-in 与无端点惰性;实现时必须在本仓重新落地、测试,并使用 `DISTILLY_*` 命名,不能把 prototype 当依赖。
约束(哲学 2.3 / 2.4):
- 没配端点就不问、不发
- 交互式问一次并记住;非交互拒绝且不落盘
- 数的是创作(蒸了、装了),承认数不到「被模型读了 SKILL.md」
- 禁止为了指标在投影里塞必调工具
---
-14
View File
@@ -1,14 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 19. 完成度的事实归属
本合同不保存实时 schema 号、测试数量、CI 历史或“已落地”清单;这些数据会随实现变化,写在设计父文和 24 个投影里会形成第二事实源。
- [architecture.md](../../architecture.md) 说明当前树实际发布什么,以及目标包是否已经出现。
- `tools/skill_schema.py`、测试发现结果和 CI workflow 是当前 schema、测试数和门禁的机械证据。
- 本节以及仓外 prototype 的历史描述都不能证明 `Distilly`、`Person`、claims、lineage、telemetry、MCP、HostInjector、Bot、面板或 marketplace 已发布。
- 每个产品 slice 落地时,同一 PR 更新 architecture、测试与 proposed/implemented Agent Note;设计合同只在目标机制改变时更新。
在 Distilly 产品代码出现前,当前 dot-skill 蒸馏和安装链只能作为迁移输入。目标中间产物仍是 `claims.jsonl` 加内核/域 Markdown,再投影 `SKILL.md`。
---
-29
View File
@@ -1,29 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 20. 主路径与成功标准
```
create(person, space)
→ 宿主扒 / 喂文件 / plan+accept
→ ingest(哈希变了才过边界)
→ pending → 宿主蒸(先 claims,再渲染,再投影)
→ commit(profile + claims + 抽到的关系)
置信度下降 → versions/vN-awaiting
→ get / prompt / install
→ correct 落 corrections,立刻改对应 claim
```
第一版 SDK 成功标准(六步):
1. 用户指定一个人
2. agent 用浏览或截图采到材料,或用户丢进导出文件
3. `ingest` 去重落盘
4. 宿主蒸馏并 `commit`
5. 下次对话 `get` 能加载这版
6. 用户改一处 `correct`,再 `get` 能看到,且 `corrections/` 里有记录
这六步过了,第一版成立。其余产品面往这几个方法后面加,不改 `Material` 和 `commit` 的形状。
Codex 插件另加第 12 节四条验收。总验收第一刀:无登录、无 key,对公开网页人物走完并 `get` 到带例句的 `voice` 和带 evidence 的 claim。
---
-18
View File
@@ -1,18 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 21. 落地顺序
1. 冻结仓库、包、路径和遥测命名;实时基线只从 architecture、源码与 CI 读取
2. 保持治理、文档、lint 和 Python 矩阵门禁真实通过,再增加产品状态
3. 搬可验证的 adapters 能力 + `Distilly`/`Person` + 进程内引擎 + 新磁盘 + 从当前 schema 迁移
4. MCP 五工具 + 宿主注入(Claude Task / Codex instructions)+ 产品 skill 禁令
5. pending / commit / 置信度闸 / corrections / 集合哈希跳过
6. 关系 jsonl + `link`/`neighbors`/`mentions`
7. 一个 bot 绑定
8. Codex/Claude marketplace 插件包(本地 stdio)
9. daemon 队列(有 key 时)
10. 面板、相似、marketplace、Direct 飞书 API
第一版明确不做:daemon 常驻定时轮询(SDK 不强制);必填 multimodal/embedding key;向量库;salience 裁剪;相似边;在 `api.py` 写死宿主路径。
---
-12
View File
@@ -1,12 +0,0 @@
> 本章由 [system-v1.md](../system-v1.md) 生成。**v1 已 deprecated**,直接继任者是 [system-v2.md](../system-v2.md);当前生效合同是 [system-v3.md](../system-v3.md)。请只编辑父文件,然后运行 `python3 scripts/sync_design_chapters.py`。
## 22. 文档怎么演进
- 改接口先改本文,再改代码。
- 只编辑父文件;`python3 scripts/sync_design_chapters.py` 生成 24 个 topic 章节,`verify_docs.py` 拒绝漂移。
- 实现开始后仓库写现在时 `docs/architecture.md`,决策进 Agent Note。
- 开放项关闭时把 4.2 状态改成 `closed YYYY-MM-DD`,把最终规则落到 §4.1 或所属章节,并链接同一变更中的 Agent Note。
- 旧 skill 测试随迁移改断言:产物路径、当前 schema → `PROFILE_SCHEMA_VERSION`。
- 仓库外的会话、canvas、clone 或未提交 prototype 不是规范来源;缺失事实先写回设计或 Agent Note。
---

Some files were not shown because too many files have changed in this diff Show More