dsh-continual-evolve
Agent & WorkflowZK-Andy/dsh-continual-evolve
A continual self-evolution plugin for DeepSeek Harness with versioned, auditable, rollback-safe harness state refined from session trajectories and a benchmark-driven validation loop.
- ai-agent
- deepseek-harness
- deepseek-harness-plugin
- dsh
- dsh-plugin
- dsh-plugins
- self-evolving-agents
- typescript
Verification and compatibility
This section shows evidence collected by the catalog. Undeclared information is labeled as unknown.
- Current-version compatibility
- Verified on the catalog Harness version
- Declared Harness range
- Not declared
- Declared platforms
- Not declared
- Profiles
- web
- Build approval
- Unknown
- Permissions
- Not declared
- External services
- Not declared
- Telemetry
- Unknown
This is not a security endorsement. Review source, permissions, and configuration before installing.
View evidence and scope
Verification covers only the named source, version, and Harness environment. It does not guarantee future compatibility.
dsh-continual-evolve@0.3.0- The manifest and static structure passed checks; a runtime load check has not completed.
README
dsh-continual-evolve
English | 中文
A workspace-memory plugin for the DeepSeek Harness (dsh): every memory lives in one central SQLite database (~/.dsh/evolve/memory.db, rows partitioned by workspace), the index is injected at session start, and the plugin's code is the literal sole writer — the model proposes, code gates and lands.
Why
Reusable experience gathered in one session (user preferences, pitfalls, project context) is forgotten by the next. A folder of markdown files makes memory visible but leaves every write ungoverned: index drift, frontmatter rot, oversized files, and secrets are only visible after a bad write lands. This plugin keeps ZCode's memory shape (per-workspace partitioning, injected index, typed memories) but moves the store into one database whose every mutation passes mechanical gates.
How it works
- Session-start injection — the plugin registers a single system-prompt section: the workspace's memory index as a store query (hooks only, whole-line truncated over budget, ordered feedback > user > reference), the central-library path, and a three-line guide. The section is computed once per session and reused byte-for-byte, keeping the system prompt stable and the prompt cache warm; an empty store costs zero tokens.
- Four tools, one writer —
memory_write(create/update/delete) is the explicit path for "remember …" / "forget …": every proposal passes the code gates (id shape, type enum, the feedback Why/How contract, secret screening carried over from v1, 64KB body cap, optionalcorrectslinks to the same-workspace active memory being superseded) inside one transaction.memory_readfetches bodies by id and searches by keyword — whitespace-separated terms are AND-combined, with literal substring matching for short terms and whenever the trigram index misses; the injected index carries hooks only. Each memory body served by id increments that memory's view counter (views + last viewed, the same presence heartbeat as skills).skill_write(create/update/delete in the project, plus an explicit move-to-global) sediments reusable procedures through the same discipline (kebab ids, secret screening, 64KB cap, no duplicate ids or bodies across scopes); every applied skill is materialized by the plugin as<workspace>/.agents/skills/<id>/SKILL.md— the database is the source of truth, the files are a read-only projection patrol restores on drift.skill_readfetches skill bodies by id and searches project plus global skills (the project shadows a same-named global); each body served by id increments that skill's view counter (views + last viewed), which the card shows as a presence heartbeat — viewed, not applied, so long-unviewed rows are removal candidates while pruning itself stays human. On hosts without the tools service the plugin degrades to "tell the user" phrasing and stays functional. - Proposal-based extraction — the trajectory-to-memory automation, audited in the
extraction_log: everyagent/turn-stoppingschedules that session's boundary, with state kept per session (a run in flight keeps only its newest boundary — coalescing, not queueing;compaction/startand session close drain it) and no idle timer. A run reads the session increment since its cursor, feeds FTS-similar existing memories to one LLM call, and the model's structured proposal lands through the same gates; failures and rejections never advance the cursor. A cheap pre-gate (increment head + ownership triple-test, tiny verdict) runs first: refusals consume the increment as a model skip without the full call, and an unclear gate fails open to the full call — worst case degrades to the old single-call behavior, never to a stall. Full and gate usages ledger in separateusage/cheap_usagecolumns. Internal agents, empty increments, turns without real user prose, and turns that already carried an explicitmemory_writeare mechanically skipped — and every skip is ledgered, consecutive quiet turns of one session as a single row carrying how many wake-ups it stands for. - Autonomous content-quality loop — the second background automation, independent from extraction by construction (own prompts, own daily budget, own
review_logledger, no cursor; only the store's write gates are shared): a cheap gate model call decides whether a memory/skill entry deserves a close look, and only approvals run the evolve pass that judges content quality (memories on home-ownership plus accuracy/usability/reusability/duplication/freshness, skills on home-ownership plus trigger precision/executability/reusability/duplication) and proposes the rewrite. The same three home tests (user/environment vs repo-version subject, name-stripped sentence, repo facts live in ADR/docs) guard the extraction prompt, so repo-version specifics never enter as memories in the first place. Gate/leg failures ledger the error text instead of a bare code. New writes are reviewed as they land, the existing stock drains through a bounded backlog queue, and untouched entries requeue after 30 days. Two hard guarantees bound the autonomy: delete never lands automatically (rewrite or quarantine only — quarantine hides, never deletes) and oversized rewrites degrade to quarantine. A daily cap (default 100 items, new > corrects-linked > stale > spot-check priority, the tail drops instead of rolling over) bounds the token spend, and every terminal path lands oneitem-reviewverdict row plus one daily rollup row (budget vs reviewed vs backlog vs dropped, tokens split by cheap/evolve purpose) for later replay. Ships in trial mode (review.dryRun, default on): verdicts ledgered, nothing applied. - Rollback is a ledger, not a mechanism — every mutation lands an
extraction_logrow with full before/after snapshots; undo means rewriting thebeforevalues. Skill mutations land the same way inskill_log, review verdicts inreview_log. A hygiene pass rides on every applied run: orphan FTS rows are dropped and secret-bearing rows are quarantined (hidden, never deleted; a quarantined skill's materialized file is removed so it stops loading). The read-only card surfaces quarantine anomalies. - Read-only card — mounted on the plugin's page in the official plugin manager (the
plugins.bundle.configslot; hosts without it silently degrade to no card), shaped like the market's own settings card and aligned with ZCode's Settings → Memory viewer: a dropdown scope selector over the store's workspace partitions (opening on the host's current workspace), a search box, per-entry relative updated times, click-to-preview bodies (5 MiB cap), patrol warnings, and a skills section listing the workspace's project skills plus visible global skills (global rows badged, project shadowing global) with the same preview, each row showing its view count. Localized through the host'slocale/*.jsonmetadata channel; host theme tokens, theButtonprimitive, and zh/en copy that tracks the DSH language. The card is a request-time projection of the database and has no edit path.
Memory types follow the ZCode taxonomy: user (profile and environment facts), feedback (corrected or confirmed practices — bodies must carry Why: and How to apply: lines), reference (resource pointers). The extraction boundary is person- and environment-scoped knowledge only: decisions and trade-offs are repo-scoped by nature and belong to the repo's ADR route, never to memory.
Legacy markdown stores (<workspace>/.evolve/memory/*.md) are imported losslessly on first contact and the directory is renamed aside as memory-imported-<date>/ — never deleted.
Install
# From npm (activates on install — ships its own bundle patch)
dsh plugin add dsh-continual-evolve
# Or from source (approve the allowBuilds build step on first GitHub install)
dsh plugin add ZK-Andy/dsh-continual-evolve
After installing or updating, restart the DSH profile you actually use (dsh web or the desktop host).
Usage
No commands. On the first session after restart the index is injected automatically. Just tell the model "remember …" / "forget …" — it lands through memory_write and the gates. To sediment a reusable procedure, tell the model to save it as a skill — it lands through skill_write in the current project and materializes as a SKILL.md the skill tool discovers after restart; say "use it everywhere" and the model moves it to the global scope (the project copy archives). Memories and skills written mid-session become visible in the next session (the section is frozen to protect the prompt cache). To see the whole store, open the plugin's page in the official plugin manager: the read-only card shows each workspace's entries and skills, search, and patrol anomalies. The store requires Node ≥ 22.5 (node:sqlite, part of the desktop host's EnsureNode runtime); without it the plugin degrades to a no-op with a console warning.
Configuration
| Key | Default | Meaning |
|---|---|---|
memoryIndex.enabled | true | Register the memory section |
memoryIndex.guide | true | Inject the guide (zero tokens when off and the index is empty) |
memoryIndex.order | 400 | Section order (upstream named slots start at PLAN_POLICY=500) |
memoryIndex.maxChars | 6000 | Hard character budget for the injected index |
memoryIndex.extraction | true | The proposal-based extraction run (turn-level, per-session slots, no idle timer) |
review.enabled | true | The autonomous content-quality loop (own prompts/budget/ledger, trial dry-run) |
review.dryRun | true | Ledger verdicts without landing them (flip off after the trial distribution reads clean) |
review.dailyBudget | 100 | Reviewed items per UTC day (new > corrects-linked > stale > spot-check, tail drops) |
review.batchSize | 10 | Max items per drain run |
memoryCard.enabled | true | Read-only memory card in the official plugin manager (degrades to no card when the host lacks webServer/plugins.bundle.config) |
Profile patch example:
- id: continual-evolve
config:
memoryIndex:
maxChars: 8000
extraction: true
review:
dryRun: false
Development
pnpm install && pnpm build # deps + clean build -> lib/
pnpm test # vitest (328 tests)
pnpm test:coverage # v8 coverage, CI-enforced thresholds
pnpm lint # oxlint src test client
pnpm check:pack # published-artifact consistency (also runs on prepack)
Layout:
├── src/
│ ├── index.ts # registration: section + tools + extraction + review + card wiring
│ ├── store.ts # central SQLite store: DDL/WAL/FTS, reads, ledger, patrol, cursors
│ ├── store-apply.ts # the sole write gate: validate all, apply atomically, ledger
│ ├── token-usage.ts # single accounting entry: normalize provider usage, pure sums
│ ├── review.ts # autonomous quality loop: cheap gate → evolve, budget, scheduler
│ ├── review-prompt.ts # review prompts + verdict parsing (gate + memory/skill rubrics)
│ ├── store-apply.ts # the sole write gate: validate all, apply atomically, ledger
│ ├── memory-rules.ts # mechanical gates: enums, sizes, id shape, secret screening
│ ├── memory-section.ts # injection: store query / truncate / session freeze
│ ├── memory-guide.ts # the main-session guide (three lines)
│ ├── memory-tools.ts # memory_write + memory_read (host-shaped definitions)
│ ├── skill-rules.ts # skill gates: field checks, SKILL.md rendering, managed paths
│ ├── skill-tools.ts # skill_write + skill_read (project shadowing global)
│ ├── extraction.ts # runner + per-session scheduler (turn-level, no idle timer)
│ ├── extraction-surface.ts # trajectory slicing: cursor, eligibility, serialization
│ ├── extraction-prompt.ts # the extraction prompt + answer parsing
│ ├── import-md.ts # lossless legacy markdown migration
│ ├── memory-snapshot.ts # card: read-only store projection (entries / patrol / degrade)
│ ├── card-routes.ts # card: GET-only read API on the host webServer
│ └── workspace-hint.ts # card: the host's current workspace as the opening default
├── client/
│ └── client.js # hand-written client bundle: read-only card in the plugin manager
├── locale/ # host package metadata: localized display name + description (zh/en)
├── test/ # vitest suite (25 files)
├── lib/ # build output (tsc)
├── docs/ # design.md (design) · FAQ.md (real-world pitfalls)
└── .agents/ # AI collaboration layer (AGENTS.md, skills, ADR notes)
Docs
- Design:
docs/design.md· Pitfalls:docs/FAQ.md· Store decision:.agents/notes/implemented/architecture/2026-10-06-sqlite-single-store.md
License
MIT. Independent project — not affiliated with DeepSeek.