From 7cb4ae670834895f0a638b2d5f5865525c3825e1 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Sat, 11 Jul 2026 09:02:35 +0700 Subject: [PATCH] Implement code changes to enhance functionality and improve performance --- .claude/plans/zesdex.plan.md | 1401 ++++++++++++++++++++++++++++++++++ 1 file changed, 1401 insertions(+) create mode 100644 .claude/plans/zesdex.plan.md diff --git a/.claude/plans/zesdex.plan.md b/.claude/plans/zesdex.plan.md new file mode 100644 index 0000000..568cac9 --- /dev/null +++ b/.claude/plans/zesdex.plan.md @@ -0,0 +1,1401 @@ +# Plan: zesdex — Rust TUI AI Coding + Security Agent (koma architectural clone) + +**Source reference**: `/mnt/code/koma/` (Rust `src-agent` + Python `src-security` + Python `src-internet`) +**Complexity**: Large (multi-session, phased delivery) + +## Summary + +`zesdex` is a from-scratch Rust rebuild of koma's full architecture: a `ratatui` TUI +agent driven by an OpenRouter-compatible LLM backend, with a rich sandboxed tool set +(filesystem, shell, git, web, memory, sub-agents), a three-layer approval harness, +MCP client support, OAuth login, a detachable daemon/client IPC split for +multi-session use, and an opt-in security-tooling module wired to a Python sidecar +daemon (mirroring `koma_sec_daemon`) for **authorized** pentesting/CTF/research use +against systems the operator owns or is explicitly authorized to test. + +Four explicit deviations from the literal 1:1 clone, all already confirmed with you: + +1. **Approval harness stays, reshaped as an opt-in speed dial, not deleted.** Auto + mode is genuinely fast (WC + TAC run inline, no `y/n` prompts, mutations proceed + immediately) — that satisfies "unrestricted auto-approve at maximum speed." What + does **not** get removed: the deterministic **catastrophic-op guard** + (`check_destructive` in `git_operator`, the recursive-delete-outside-workspace + check, force-push-over-history, secret/credential-file exfil patterns in + `web_download`/`bash`). That guard is not a per-turn approval prompt — it never + blocks routine work — it is a hard-coded refusal on a short, named list of + irreversible operations, and it fires even in Yolo/Auto mode. Everything else + (file writes, edits, ordinary shell commands, git commits/branches, web fetches) + runs with zero prompts in Auto/Yolo mode, exactly as requested. +2. **Security toolkit ships as an explicit opt-in module** (`/security` panel, + disabled by default, requires the user to explicitly enable it per koma's own + `security_enabled`/`yolo_armed` gating pattern) with a startup banner and + `--security-install` flow that states the authorized-use scope, mirroring + koma's own "curated, opt-in suite... for authorized testing and research" + framing. `sec_*` tools remain harness-exempt (per koma's own design — these are + explicit user-invoked offensive actions, not silent mutations to gate), but the + catastrophic-op guard's network/destructive-target patterns still apply where + they'd protect the *operator's own machine* (e.g., a `sec_z3`/`sec_sage` script + that shells out to `rm -rf /`). +3. **koma's `task` subagent primitive (Phase 6) is replaced wholesale by a dynamic + workflow orchestration engine**, modeled directly on the `Workflow` tool this + very session runs on — not on anything in koma, which has no equivalent. See + "Dynamic Workflow Orchestration" in the digest below and the rewritten Phase 6. + Three decisions you confirmed: + - **Fan-out is model-decided, not forced.** Simple requests are handled inline by + the main agent with its own tools, exactly as today; the model only invokes the + workflow engine when it judges a task benefits from decomposition, parallelism, + or independent verification passes. + - **Full replacement, not a layer on top.** There is no separate simple + "delegate one subagent" tool anymore — a single-agent delegation is just a + one-line workflow script (`agent(...)` with no `parallel`/`pipeline` wrapper), + so the workflow engine is the *only* delegation surface, keeping one mental + model instead of two. + - **UI: a separate live progress-tree panel**, auto-shown the instant a workflow + starts (phase → agent → status), dismissible/re-summonable without killing the + run, while the Main Execution Log keeps behaving exactly as specified for + everything else. +4. **NEW — Reasoned mutations + self-learning quality loop**, added after your + latest request: every `write`/`edit` tool call must carry a required `reason` + argument (rejected deterministically, before touching the filesystem, if + missing/empty), so every mutation is auditable after the fact. Once an agentic + turn finishes and any files were touched, zesdex automatically — no model + decision involved — spawns a detached background subagent (reusing Phase 6's + subagent engine in koma's existing detached-nudge mode) that reviews the + touched files against their stated reasons and the surrounding code's own + conventions, and folds anything worth keeping back into per-project memory as + a new `lesson`-typed entry. Because the memory index is already injected into + every future turn's system prompt (koma's existing mechanism, unchanged), this + closes a real feedback loop: code quality compounds across turns instead of + the agent repeating the same mistake every session. See "Reasoned Mutations & + Self-Learning Quality Loop" in the digest below and the new Phase 7. + + Following your explicit request for concrete self-learning feature + suggestions, four core additions and three advanced additions were confirmed + and are folded into the same digest section and Phase 7 (advanced ones note + their own phase where they land instead): **outcome-linked learning** (lessons + backed by real `build`/`test` results, not just LLM opinion), **lesson + lifecycle** (decay/staleness + contradiction detection, never silent auto- + delete), **human-in-the-loop calibration** (a keep/discard toast before a + lesson becomes permanent, auto-keep in Auto/Yolo but always visible/ + reversible), and a **global cross-project lesson tier** (mirrors the Builtin→ + Global→Session tiering Phase 6 already builds for agent definitions) as core + Phase 7 scope; **batch/offline pattern mining**, a **`/learning` dashboard** + (delivered in Phase 14 alongside the rest of the remaining UI surface), and + **escalation on repeated violations** as the advanced layer on top. + + A follow-up round of suggestions added three more confirmed additions, all + still Phase 7/Phase 14 scope: **lesson graduation into a real lint check** + (a lesson that survives repeatedly at `confidence: verified` stops being + just injected text and becomes a deterministic pre-write check), a + **session-end retrospective with per-lesson provenance** (one holistic + summary per session, plus every lesson traceable back to the exact turn/ + edit that produced it), and **user-authored lessons with consensus-gated + global promotion and token-budget visibility** (`/lesson` to author a + `confidence: human` entry directly, a style-guide import path, 2-of-2 + independent-reviewer agreement required before any lesson reaches `global` + scope, and the reviewer loop's own token spend surfaced on the usage + dashboard). Per your explicit requirement, **every one of these — and + everything already in Phase 7 — is language-agnostic by design**: nothing + in the self-learning loop hard-codes Rust or any single ecosystem + (build/test detection, lint graduation, and lesson content all generalize + across whatever language a given project/workspace is written in — zesdex + itself being written in Rust is incidental to this feature, not a + constraint on it). + + A third round added six more confirmed additions, still Phase 7/Phase 14 + scope: **shadow-mode enforcement** (a lint-graduation candidate runs silent + for a trial window, logging would-have-fired counts before ever surfacing + to the model, so a blunt rule is caught before it annoys anyone), **cross- + subagent learning within one workflow run** (a finding from one `agent()` + thunk can reach sibling thunks still running in the *same* `workflow_run` + call, not just the next turn/session via memory), **adaptive review + frequency** (consecutive empty review passes throttle the reviewer down; + fresh violations un-throttle it back up — the token-spend counterpart to + escalation), **quality trend metrics on the usage dashboard** (build/test + pass-rate, escalation count, and per-lesson trigger count charted over + time, with lessons that fire often but never correlate with fewer + escalations flagged as `forget` candidates), **structured lesson bodies** + (a `before`/`after` snippet pulled straight from the triggering diff, + alongside the prose description, so a lesson is concretely actionable, not + just abstract), and **lesson-pack export/import** (an opt-in, portable + bundle of a project's lessons for moving to another machine or sharing + with a team, independent of a shared `~/.zesdex/`). + + Per your request to focus on the plan itself before any code, Phase 7 — + which had grown to cover all seventeen self-learning additions across + three rounds of suggestions — is now **split into Phase 7 (MVP core: + `reason`, the ledger, the automatic reviewer) plus four independently + deferrable sub-phases 7B (outcome-linking/lifecycle/calibration/global + tier), 7C (escalation/adaptive frequency/batch mining), 7D (lint + graduation, shadow-mode gated), and 7E (retrospectives/provenance/ + user-authored/consensus/export/trend metrics)**, restoring the + "independently buildable/testable" property every other phase in this + plan already has. Phase 7 alone is a complete, working answer to your + original request; 7B–7E are refinements you can build immediately after + or defer indefinitely. See the rewritten Phase 7 family in Tasks below. + +No comments in generated Rust source, per your original instruction — self-documenting +naming instead. No stubs; every module in a phase is fully implemented before that +phase is considered done. Cargo.toml matches your requested metadata exactly. + +## Reference Architecture Digest (from `/mnt/code/koma/`) + +Captured here so later phases can cite it without re-reading the whole reference tree. + +**Workspace**: Cargo workspace with a single `agent` binary crate (`src-agent`), +edition 2021, plus two out-of-process Python sidecars (`src-security`, +`src-internet`) invoked via subprocess/daemon, not compiled in. + +**Core dependency set** (from `src-agent/Cargo.toml`, informs zesdex's `Cargo.toml`): +`ratatui 0.30`, `tokio` (`rt-multi-thread, macros, sync, time, net, io-util, signal`), +`reqwest` (`json, stream, blocking, native-tls-vendored`), `serde`/`serde_json`/ +`serde_yaml_ng`, `anyhow`, `uuid` (`v4, v5`), `dirs`, `futures-util`, +`pulldown-cmark`, `syntect` (`default-fancy`), `rusqlite` (`bundled`), `ignore`, +`regex`, `globset`, `infer`, `base64`, `sha2`, `libc`, `rmcp` (MCP client: +`client, transport-child-process, transport-streamable-http-client-reqwest, macros`), +`include_dir`, plus web-scraping-adjacent crates (`dom_smoothie`, `fast_html2md`, +`scraper`, `url`, `percent-encoding`) for the lightweight (non-Python) web-fetch path. + +**MVC data flow**: `KeyEvent → controller/input::handle_key → Action → +runtime/actions::apply_action → state mutation → view::draw`. Slash commands: +`controller/command::parse → Command → runtime/commands::apply_slash`. The +controller layer never mutates state; `apply_action`/`apply_slash` own all +mutation and async spawns; the view is read-only. + +**Async/streaming bridge**: one `tokio::runtime::Runtime` created in `main.rs`; the +main loop is synchronous (`try_recv`/poll, no `.await` in the hot loop). One +`mpsc::unbounded_channel` per request stored in `active_rx`; harness verdicts ride +a **separate** `harness_rx` channel so they never interleave with stream tokens. +`StreamEvent` variants: `Token, Reasoning, Usage, ToolCalls, Done, Error, Compacted, +HarnessVerdict`. + +**Agentic loop**: model streams `tool_calls`; on `Done`, `advance_turn` commits the +assistant message, then either ends the turn or runs `process_tools` → gate/execute +each call → `finish_tool_round` → re-invoke the model. Loop continues until no more +tool calls or `MAX_AGENT_STEPS` (40) is hit. No forced plan gate — tools run on the +first call unless the model explicitly requests Plan mode via `plan_enter`. + +**`Tool` trait**: `name() -> &'static str`, `description() -> &'static str`, +`parameters() -> Value` (JSON Schema), `run(&self, ctx: &ToolCtx, args: &Value) -> +Result`. `ToolCtx` carries `workspace`, `workspaces: Vec`, +`dir_cache`, plus (per the fuller subagent report) memory dir, MCP manager, security +manager, bash-saving flag. + +**Deterministic tool inventory** (`tool_is_risky`: `write | delete | edit | bash | +git_operator | web_download`): `read, grep, glob, write, edit, delete, bash, +bash_output, bash_kill, cd, dir_list, dir_cache_update, pong, remember, forget, +recall, task, task_output, task_kill, todowrite, web_fetch, web_search, +web_download, git_cred, git_operator, git_worktree, plan_enter, plan_ready, +seqthink` — plus dynamic `mcp__*` and `sec_*` tools. `DEFERRED_TOOLS` (blocking I/O +tools) run on a plain `std::thread`, never inline on the event loop or inside the +tokio runtime directly. + +`write`/`edit` (koma reference, `tool/fs/write.rs` + `tool/fs/edit.rs`): `write` +takes `path` + `content`, always creates parent dirs, always writes/overwrites, +reindexes the dir cache. `edit` takes `path` + `old` + `new` + optional +`replace_all`, requires the file to exist, refuses (returns a string result, not an +error) if `old` is missing or non-unique unless `replace_all=true`. Neither carries +any notion of "why" in koma today — zesdex's `reason` requirement (see below) is a +net-new addition on top of this exact shape, not a koma pattern. + +**Three-layer harness** (`app/harness.rs`): WC (workspace containment check, +deterministic, gates the whole turn) → PC (advisory prompt classifier, fire-and- +forget, fail-open) → TAC (per-risky-call classifier, intent-aware, mode-dependent +fail-open/fail-closed policy). `CLASSIFY_TIMEOUT = 12s`. Modes: `Auto, Normal, +Plan, Yolo` (`AgentMode::cycle`), Yolo requires explicit arming +(`yolo_armed`) and bypasses TAC/`y/n` entirely — only deterministic guards remain. + +**`shell_filter`**: post-processes `bash`/`git_operator` stdout — smart per-command +filters (cargo, git status/log/diff) tried first, static regex spec table +(npm/pip/docker/make/wget-curl) as fallback, full raw output always teed to disk +when trimmed/truncated/non-zero-exit. + +**`git_operator`/`git_worktree`**: direct argv exec (no shell), SSH key injection +via `git_cred`, `GIT_TERMINAL_PROMPT=0`, and a **named destructive-op guard** +(`check_destructive`) requiring explicit `confirm_destructive=true` for force-push, +`reset --hard`, `clean -f/-d/-x`, `branch -D`, force checkout/switch/restore, +`stash drop/clear`, `tag -d`, `update-ref -d`, `filter-branch`, `gc --prune`. +Worktrees live in a shadow dir outside the repo. + +**Subagents**: up to 5 concurrent (`MAX_SUBAGENTS`), agent definitions are +frontmatter+markdown files merged Builtin→Global→Session tier, isolated +`Conversation` seed (own system prompt, no shared history with parent), own +restricted tool allow-list (always minus `task`/`task_output`/`task_kill`, and +zero MCP tools), engine runs the same commit/execute/classify loop non- +interactively with fail-closed risky-tool gating (no human to ask), completion +folds back via blocking-park / chat-append / **detached-nudge** depending on +invocation mode. The detached-nudge mode matters most for zesdex: it lets a +subagent run fully off the critical path and only surface a synthetic-user-turn +nudge (or, per Phase 7, a collapsed system note) once the session goes idle, +without blocking whatever the user does next. + +**Background bash**: `run_in_background: true` bash calls spawn a plain OS thread +(not tokio — child process needs to run outside the runtime), answer the model +immediately with a job id, poll via `bash_output`, kill via `bash_kill` +(SIGTERM to the recorded pid), completion is toast + buffered synthetic-user-turn +nudge once the session goes idle. + +**IPC/daemon**: **daemon-per-session**, not daemon-per-machine — each session UUID +gets its own headless process at `~/./run/.sock`. Transport: Unix +domain socket, 4-byte-BE-length-prefixed JSON frames (`FrameReader` reassembles +split/coalesced reads, 64 MiB hard cap enforced at the prefix). `bind()` success is +the sole liveness oracle (never a PID file). Per-client connection task splits +into independent async read/write halves bridged to the **synchronous** daemon +loop via `std::sync::mpsc` (daemon loop stays sync so it shares turn-servicing +code with a hypothetical standalone/no-daemon mode). `ClientRequest`/`DaemonFrame` +message sets cover attach/detach/list/status/submit/shell/key/paste/approve/plan- +decision/new/quit/switch-foreground. Multiple clients can attach one daemon +(controller + viewers, promotion on controller disconnect). Self-exit after a +grace window once all sessions are tombstoned and no client is enrolled. + +**Snapshot/diff**: daemon builds a pure `StateSnapshot` from live `AppState`; +per-client `diff()` decides `needs_full` (structural change → resend full +snapshot; correctness-first, "when in doubt, ask for a full snapshot") vs a +cheap `Vec` for pure-append/status/scroll changes. Client applies +frames into a **shadow `AppState`** and reuses the *same* `view::draw` renderer, +so daemon and thin-client rendering can never diverge. + +**Session locking**: one `session.lock` file per session holding the owner PID; +liveness via `kill(pid, 0)` (portable, not `/proc` parsing); stale locks +opportunistically cleared. Lock always written with the **daemon's** PID, never +the thin client's. + +**MCP**: `rmcp`-based client, two transports (stdio child process, streamable +HTTP), tools namespaced `mcp____`, `Local` vs `Proxy` +(shared global MCP daemon) backend behind one facade so multiple session-daemons +don't each spawn duplicate heavyweight MCP servers, sync↔async bridge for +`Tool::run` compatibility, 20s connect / 60s call timeouts, non-blocking +background connect/reconnect with a generation counter to discard stale in-flight +connects. + +**OAuth**: PKCE for the primary provider (loopback `TcpListener` on a fixed local +port for the redirect, hand-parsed HTTP, CSRF `state` check), device-authorization +polling for a secondary provider, unverified local JWT payload decode for display +metadata only, single-flight per-provider refresh with a stale/near-expiry lead +window and an `unrecoverable` latch on `invalid_grant`. + +**TUI layout**: header (mode indicator) → transcript (scrollable, markdown- +rendered assistant turns, plain-text user/streaming turns) → optional pending- +steer panel → model-name row → input box (dynamic height, capped at half the +frame) → status bar. Markdown rendering emits pre-wrapped `Vec>` so +line count matches on-screen rows exactly (needed for scroll math); `syntect` +lazy-singleton highlighting in fenced code blocks with hard-splitting on +over-width lines; tables/lists/blockquotes word-wrap via a shared char-level +wrapper. Theme: a `Palette` struct of ~12 semantic colors + a 5-stop heatmap +ramp, computed fresh per frame from config, several named palettes plus an +accent-color resolver. + +**Mode state machine**: far more than "5 modes" — onboarding, provider-OAuth +onboarding, credentials form, session picker/hub, chat, loading splash, settings, +agents editor, MCP manager, help, effort picker, usage dashboard, message rewind, +quit confirm, security panel, background-bash panel, todo panel. Each mode is a +struct variant on one `Mode` enum, exactly one active at a time, stored per-session. + +**Security sidecar** (`koma_sec_daemon` equivalent): long-lived Python child +process, newline-delimited JSON frames over stdin/stdout (not a socket) — +handshake with a shared token, `call`/`health`/`install` ops, single-threaded +serialized dispatch. Tool roster spans web (http, sqlmap, nuclei, ffuf, dalfox, +zap, xss-confirm), crypto (z3, sage, rsa, factordb, lattice/fpylll, hashcat, +hashid, generic decode), reverse-engineering (js deobfuscate/unminify, source-map +recovery, wasm decompile), and pwn (static triage, ROPgadget, pwntools remote +session, exploit-template scaffold). Sandbox = wall-clock timeout + optional +RLIMIT_AS/RLIMIT_CPU + optional (opt-in, currently-unused-upstream) bubblewrap +network unshare — no container/chroot/seccomp. Tiered installer (pip / GitHub- +release binary / gem / manual-detect-only) with a health-probe surfaced in the UI. + +**Internet sidecar** (`scrapion_agent` equivalent): **stateless**, one-shot +subprocess per call (`python -m ... --json ""`), Playwright-Firefox- +headless search+scrape with stealth tweaks, HTML→Markdown conversion, JSON out on +stdout. Rust side spawns it on a plain OS thread (not tokio) with a `recv_timeout` +guard and falls back to a raw HTTP fetch on any failure. + +**Dynamic Workflow Orchestration** (new — not present in koma; modeled on this +session's own `Workflow` tool): a script-driven orchestration layer that lets the +main agent decompose a task into many subagents running in parallel, pipeline, or +phased stages, rather than koma's one-subagent-at-a-time `task` primitive. + +- **Trigger**: exposed to the model as one new tool, `workflow_run(script, args)`. + There is no separate "spawn one subagent" tool — a single delegated task is + simply a one-`agent()`-call script, so `workflow_run` is the sole delegation + surface. The system prompt instructs the model to reach for it when a task + benefits from decomposition/parallelism/independent verification, and to skip + it (use its own `read`/`grep`/`edit`/`bash` directly) for anything simple — + this is model judgement, not a hard trigger rule, matching your "dynamic, model + decides" choice. +- **Script primitives** (a small embedded scripting surface, not a general + scripting language — deliberately narrow so it stays auditable and + serializable into the persisted transcript): + - `agent(prompt, opts?) -> AgentResult` — spawn one subagent (own isolated + conversation seed, own restricted tool allow-list, same non-interactive + engine loop as koma's subagent engine: commit/execute/classify with + fail-closed risky-tool gating since there's no human to ask). `opts` covers + `label`, `phase`, `model` override, `effort` override. + - `parallel(thunks) -> Vec` — run N agent thunks concurrently, + barrier: all must finish before returning. Bounded by a concurrency cap + (mirrors koma's `MAX_SUBAGENTS`-style ceiling, default 5, configurable). + - `pipeline(items, stage1, stage2, ...) -> Vec` — run each item through + every stage independently with **no barrier between stages** (item A can be + on stage 3 while item B is on stage 1); the default recommended shape for + multi-stage fan-out because wall-clock is the slowest single chain, not the + sum of slowest-per-stage. + - `phase(title)` — label subsequent `agent()` calls under a named phase for + the progress-tree UI; purely cosmetic/organizational, no execution effect. + - `log(message)` — narrator line surfaced in the progress panel above the tree. +- **Execution model**: `workflow_run` itself is a `DEFERRED_TOOL` (per the + existing `DEFERRED_TOOLS` pattern) whose body runs on the tokio runtime (it + needs real concurrency for `parallel`/`pipeline`, unlike the blocking-thread + tools); each `agent()` call inside it reuses the exact same subagent spawn/ + engine/fold-back machinery Phase 6 already needed for koma parity — so + `workflow_run` is a thin orchestration layer *over* individual subagent + spawns, not a reimplementation of subagent execution. Concurrency, isolation + (own `Conversation` seed, own tool allow-list, zero recursion into + `workflow_run` itself to bound blast radius), and the fail-closed risky-tool + gate are all inherited unchanged from the subagent engine design. +- **Result delivery**: `workflow_run` blocks the calling turn (like a normal + tool call) and returns a structured summary (per-agent status + final return + value) as the tool result once every stage completes; the model then + synthesizes a response from that structured result, same as any other tool + round-trip — no separate detached/nudge mode for v1 (koma's detached-task + nudge pattern is not carried over here, since workflow runs are expected to be + the user-visible unit of work, not a background fire-and-forget job). +- **TUI surface**: a dedicated **Workflow Progress panel**, auto-shown the + instant `workflow_run` starts (overlaying/pushing above the transcript region, + matching koma's overlay pattern for `Bash`/`Todo`/`MessageRewind` modes — same + `layout_chunks` reuse, new overlay content), rendering a live tree: phase → + per-agent row → status glyph (queued/running/done/error) → elapsed time. + Dismissible with Esc without killing the run (run keeps streaming in the + background; re-summon via a key binding or `$`-style panel toggle, mirroring + koma's sub-agents panel UX). On completion, the panel's final state persists + in scrollback as a collapsed summary block in the Main Execution Log, and the + panel auto-closes after a short delay unless pinned open. +- **Persistence**: each `workflow_run` call and its final structured result is + logged to the session transcript like any tool call/result pair — no separate + workflow-history store for v1. + +**Reasoned Mutations & Self-Learning Quality Loop** (new — not present in koma; +added for your "tiap edit atau write harus ada parameter alasan... bg agent untuk +verifikasi kualitas... self learning" request): + +- **`reason` becomes a required argument on `write` and `edit`** (koma's + reference shapes above gain one field each): `{"path", "content", "reason"}` + for `write`, `{"path", "old", "new", "replace_all"?, "reason"}` for `edit`. + Enforcement is deterministic and happens *before* any filesystem call, exactly + like the existing `arg_str` required-field pattern already used for `path`/ + `content`/`old`/`new` — a missing or blank `reason` returns + `error: missing required string argument 'reason'` and the file is never + touched. This is intentionally the same shape as `tool_is_risky`/WC: a name- + based, always-on, non-negotiable check, not something the classifier or agent + mode can waive. `delete` and `bash` are explicitly NOT changed — the user's + ask was scoped to edit/write, and widening it would duplicate what the + harness/catastrophic-op guard already cover for those. +- **Every accepted call appends one line to an append-only per-session ledger**, + `/edits.jsonl` (same directory tier as `session.lock`/ + `settings.json`, same atomic-append discipline as `model::memory::atomic_write` + uses for whole-file writes — here it's an `O_APPEND` line write, since the + ledger is never rewritten in place). Each line: `{ts, tool, path, reason, + content_sha256, bytes_delta}`. The ledger is the audit trail the reviewer + (below) reads; it is never truncated or rotated automatically in v1. +- **Auto-triggered review, not model-invoked.** When `advance_turn` reaches a + turn boundary (no more `tool_calls`, same point `Done` already fires today) + and that turn's slice of `edits.jsonl` is non-empty, the event loop — not the + model, not a tool call — spawns exactly one subagent using Phase 6's existing + engine in koma's **detached-nudge** invocation mode, seeded with a new + built-in agent-definition persona, `quality-reviewer`. Its tool allow-list is + read-only plus memory: `read, grep, glob, recall, remember`. It never sees + `write`/`edit`/`bash`, so it cannot itself mutate anything — a review pass can + only ever produce a memory note or nothing. + - Input: the turn's ledger lines (path + reason + diff-relevant content) plus + a `recall` of the project's existing `lesson`-typed memories, so the + reviewer can check "have I already said this" before writing a new one — + this is what keeps `MEMORY.md` from growing an unbounded stream of + restated observations. If an existing lesson already covers the point, the + reviewer does nothing (no duplicate `remember` call). + - Output: zero or more `remember(..., type="lesson")` calls for anything + genuinely worth carrying forward (a real convention violated, a recurring + smell, a reason that didn't match what the diff actually did), plus exactly + one short verdict string returned as the subagent's final result. + - The verdict is rendered as a **collapsed system note** in the Main + Execution Log (a new `StreamEvent`-adjacent variant, e.g. `SystemNote`, + routed the same way `Compacted`/`HarnessVerdict` already ride their own + channel so it can never interleave with live stream tokens) — it is + explicitly NOT appended to the model-visible conversation history, so + review passes cost the reviewer's own tokens only and never grow the main + agent's context. + - Because `model::memory::load_memory_index` already injects the whole + `MEMORY.md` index (now including any `lesson` entries) into every future + turn's system prompt, this is the entire feedback loop — no new injection + plumbing needed, only the new `kind` value and the auto-trigger. +- **Bounded, not free-running**: at most one reviewer subagent runs at a time + (reviews queue rather than pile up — reuses the same bounded-concurrency + primitive Phase 6 builds for `parallel()`, capacity fixed at 1 for this + queue); the trigger is skipped entirely while `AgentMode::Plan` is active + (nothing mutates in Plan mode, so `edits.jsonl` can't have a non-empty slice) + and skipped for edits made *by* a reviewer or workflow subagent itself (no + recursive review-the-reviewer chains — ledger lines carry an `origin: main | + subagent | reviewer` tag precisely so this filter is a cheap equality check, + not a heuristic). +- **Explicitly reuses, does not reinvent**: Phase 6's subagent spawn/engine/ + fold-back machinery (detached-nudge mode already exists there for other + reasons); `model::memory`'s `write_memory`/`read_memory`/`slugify` primitives, + widened by one `kind` value; the existing pattern of a dedicated side-channel + for out-of-band events (`harness_rx` today, a parallel `notes_rx` for + `SystemNote`). + +**Outcome-linked learning** (language-agnostic by design — zesdex itself being +a Rust project is incidental; the mechanism below never assumes Rust): the +reviewer's input is upgraded from "reasons + diff only" to "reasons + diff + +real verification result." Detection of "how do I build/test this project" +is a small ordered probe, not a hard-coded `cargo` call: a `verify_command` +in `settings.json` always wins if the user sets one; otherwise zesdex probes +for well-known project markers already visible to `dir_cache` (`Cargo.toml` → +`cargo build && cargo test`, `package.json` → its `scripts.test`/`scripts. +build` if present, `pyproject.toml`/`requirements.txt` → `pytest`/`python -m +pytest` if discoverable, `go.mod` → `go build ./... && go test ./...`, and so +on for the other common ecosystems); if no marker matches, verification is +simply skipped for that project and the reviewer runs on reasons+diff alone. +This probe table lives in one named function (mirroring the `tool_is_risky`/ +catastrophic-op-guard pattern of "one place to extend"), so adding a new +ecosystem later is a one-line addition, not a redesign. The resolved command +runs on a plain `std::thread`, capped at a short timeout, and hands the +reviewer the pass/fail + trimmed output alongside the ledger. A lesson born +from an actual build/test failure is written with `confidence: verified`; a +lesson born from the reviewer's own judgement alone (no reproducible failure) +is written with `confidence: opinion`. Both render in the memory index, but +`opinion` entries are visually distinguished (dimmer/marked) in every mode +that lists memories, so verified lessons are never confused with the +reviewer's own guesses. If the verification command itself is unavailable or +misconfigured, the reviewer still runs on reasons+diff alone — this is a +strict enhancement, never a hard dependency. + +**Lesson lifecycle**: every `lesson` entry gains two frontmatter fields beyond +koma's existing `name/description/type` — `confidence` (`verified | opinion`, +from outcome-linked learning above) and `last_confirmed` (a timestamp, bumped +whenever a later review cites the same lesson as still applicable). A +lightweight idle-time sweep (piggybacks on the same idle-detection the +background-bash completion nudge already uses, no new polling loop) flags — +never silently deletes — any `lesson` whose `last_confirmed` is older than a +configurable staleness window as `stale: true` in its frontmatter; a stale +lesson still gets injected into the index (so it's still visible) but is +labeled so the model and the user both know it may no longer hold. Separately, +before any new `remember(type="lesson")` call is accepted (both from the +regular reviewer and human-in-the-loop confirmation below), a cheap +recall-and-compare pass checks the new lesson's text against existing lessons +for direct contradiction (e.g. "always use X" vs. a new "never use X"); a +detected contradiction blocks the auto-save and instead surfaces both entries +side by side in the calibration toast for a human decision — contradictions +are never silently resolved by whichever write happens to land last. + +**Human-in-the-loop calibration**: a `remember(type="lesson")` call from the +quality-reviewer does not go straight into `MEMORY.md`. It lands first in a +small pending queue (one more small JSON file alongside `edits.jsonl`, +`pending_lessons.json`, same atomic-write discipline) and renders as a +collapsed toast in the Main Execution Log: `Lesson baru: +[keep]/[discard]`, using the same `SystemNote` side-channel Phase 7 already +adds. In Auto/Yolo mode the toast auto-resolves to `keep` after a short grace +window (matching the "unrestricted, no prompts" mandate for routine flow) but +the toast and its outcome remain visible/scrollable in the log and reversible +at any time via `forget`; in Normal/Plan mode (where a human is already +expected to be attentive) the toast waits for an explicit keypress instead of +auto-resolving. Either way, nothing about this blocks the *main* agent's own +turn — calibration only gates the reviewer's side-channel writes, never the +user-facing conversation. + +**Global cross-project lesson tier**: mirrors the Builtin→Global→Session +tiering Phase 6 already builds for agent-definition files. A `lesson` memory +gains a `scope` field (`project | global`); `project` is the existing default +(`/memory/`, unchanged), `global` writes instead to a new +`~/.zesdex/memory/` store shared across every project. The reviewer defaults +every lesson to `project` scope; a lesson is only promoted to `global` when +either (a) the human-in-the-loop toast is used to explicitly promote it, or +(b) the escalation-on-repeated-violations mechanism (below) fires the same +lesson's underlying pattern across two or more distinct projects — never +silently promoted by the reviewer's own judgement alone, since a +project-specific convention wrongly promoted to global would pollute every +other project's context. `load_memory_index` (Phase 4/existing koma mechanism) +is extended to concatenate the global index ahead of the project index when +building the system prompt, so global lessons are always present and project +lessons can still override/refine them by appearing after. + +**Batch/offline pattern mining** (advanced, Phase 7 scope but lower priority +within it): beyond the per-turn reviewer, an idle-triggered (same idle +detection as the lifecycle sweep) deeper pass periodically walks the full +`edits.jsonl` history (not just the latest turn's slice) plus `git log -p` +for the same file set, looking for cross-turn patterns a single-turn reviewer +structurally cannot see (e.g., "every time `parser.rs` changes, the matching +test file does not"). Runs as one more detached subagent, same allow-list and +confidence/calibration path as the per-turn reviewer — it is the same +mechanism at a longer time horizon, not a separate code path. Rate-limited to +at most once per idle window so it can never compound with the per-turn +reviewer into runaway subagent spawning. + +**Escalation on repeated violations**: every accepted ledger line is checked +(cheap substring/embedding-free heuristic first — a lesson's `slug` matched +against the touched file's recent `edits.jsonl` history — deferred to the +reviewer's own judgement only when the heuristic is ambiguous) against the +existing lesson index for a *repeat* violation of something already +`remember`ed. On the third repeat of the same lesson within a configurable +window, zesdex stops treating it as routine background learning and instead +surfaces a non-collapsed, attention-grabbing note directly in the Main +Execution Log (not the quieter `SystemNote` styling used for ordinary +verdicts) — the premise being that if index-injection alone hasn't stopped +the pattern after three tries, silently trusting the system prompt again is +not working and a human should see it. This counter is also the trigger +condition that can promote a lesson from `project` to `global` scope per the +tiering feature above, when the same pattern repeats across multiple distinct +project directories rather than one. + +**Lesson graduation into a lint check** (language-agnostic — the check itself +is expressed as a pattern-matching rule against the tool call's own arguments/ +diff text, never a Rust-specific AST): once a lesson has stayed at +`confidence: verified` and been cited by the escalation counter above at +least once (i.e., it has already proven itself both true and still being +violated), it becomes eligible for graduation. A graduated lesson gains a +`check` frontmatter field — a small structured rule (substring/regex against +the file's new content, or a path-glob + required-companion-file rule, e.g. +"editing `parser.*` without also touching a matching `*test*` file") — and +from that point on is enforced the same way the `reason` argument is: a +deterministic, always-on check inside `write`/`edit`, evaluated *before* the +harness/classifier layer, same shared pattern as `arg_str`. A failed +graduated-lesson check does not silently block the write; it returns a +result string naming the lesson and asking the model to address it or +explicitly override with a `reason` that acknowledges the exception — so +graduation raises the bar without ever becoming an unbreakable wall. Only a +human (via `/learning`, Phase 14) or the consensus-promotion mechanism below +can graduate or de-graduate a lesson — the reviewer itself cannot +self-promote into an enforced check, for the same "no single opinion becomes +policy" reason `global` promotion is gated. + +**Session-end retrospective + per-lesson provenance**: every `lesson` memory +gains a `provenance` field recording the session id + turn index + ledger +line hash it was born from (or, for a `/lesson`-authored entry below, a +literal `"user"` marker instead) — a cheap addition since all of that is +already available at the point `remember` is called, purely for later +auditability (`/learning` can jump from a lesson straight to the originating +edit). Separately, when a session ends (explicit `/quit` or the daemon's +tombstone-on-idle path, Phase 11), one more detached subagent — reusing the +identical `quality-reviewer`-style engine, not a new code path — is hand a +session-wide view (every ledger line + every lesson touched this session, +not just the last turn) and produces a single holistic summary memory +(`type: retrospective`, one per session, not one per turn) capturing things a +per-turn reviewer structurally cannot ("this session repeatedly restructured +the same module," "three different lessons this session all pointed at the +same underlying gap"). Retrospectives are additive and separate from +`lesson` entries — they are not injected into the system prompt by default +(they're a human-facing summary, browsable from `/learning`), so they cannot +bloat future turns' context the way an unbounded `lesson` count could. + +**User-authored lessons, consensus-gated global promotion, token-budget +visibility**: a new `/lesson ` slash command lets the user author a +`lesson` memory directly, tagged `confidence: human` — a rank above both +`verified` and `opinion`, since it is authoritative by construction and +therefore exempt from the contradiction-block/calibration-toast path +entirely (a human overriding a prior automated lesson is always accepted +immediately, never queued). A companion `/lesson import ` command feeds +an existing convention document (`CONTRIBUTING.md`, a style guide, any +markdown file the user points at) through the same `quality-reviewer`-style +subagent to extract candidate lessons in bulk, seeding the project's memory +from day one instead of starting from zero — extracted entries still land at +`confidence: opinion` (the source document is trusted, but the *extraction* +is still an LLM judgement call) and go through the normal calibration toast +before becoming permanent. Global promotion, already gated behind either +explicit human calibration or the multi-project repeated-violation counter +(per the tiering feature above), is tightened further: an automatic +(non-`/lesson`) promotion additionally requires two independent reviewer +subagents (spawned fresh, no shared context between them — cheap since +review subagents are already isolated by design) to agree the pattern +generalizes, before it's written to `~/.zesdex/memory/`; a single reviewer's +opinion is never sufficient for a `global`-scope write. A `/lesson`-authored +entry can still be promoted to `global` directly by the user with no +consensus requirement, same "human is authoritative" exemption as above. +Finally, every reviewer/retrospective/batch-mining subagent's token usage is +tagged with a `origin: self-learning` cost-ledger category (reusing whatever +per-turn cost tracking the usage dashboard, Phase 14, already accumulates for +the main agent) and surfaced as its own line item there, so the self-learning +loop's token cost is always visible against the session total rather than +invisibly folded into "background work." + +**Shadow-mode enforcement**: a lint-graduation candidate (per the graduation +mechanism above) does not go straight from `confidence: verified` to an +active `check`. It first spends a configurable trial window (N subsequent +`write`/`edit` calls against matching files, default a small fixed count) in +`shadow: true` state: the same rule evaluation runs on every matching call, +but a match only increments a would-have-fired counter on the lesson's own +frontmatter — it never returns anything to the model and never blocks +anything. Only once the trial window closes does the rule either graduate to +a real active `check` (per the existing enforcement path) or, if its +would-have-fired rate looks too indiscriminate (fires on most edits to a +matching file rather than the specific pattern it was meant to catch — a +simple ratio threshold, not a judgement call), get flagged back down to a +plain `opinion` lesson instead of ever reaching enforcement. This reuses the +exact same rule-evaluation code path enforcement already needs (Phase 7); the +only new state is the `shadow`/`trial_count` pair on the lesson and the one +branch deciding graduate-vs-demote when the window closes. + +**Cross-subagent learning within one workflow run**: today, a finding one +`agent()` thunk makes only reaches the rest of the system on the *next* turn, +once the reviewer has run and the memory index has been rebuilt — too late +to help sibling thunks still executing in the *same* `workflow_run` call. +`workflow_run`'s existing execution context (Phase 6) gains one small +in-memory, run-scoped broadcast channel (not persisted, not `MEMORY.md` — +this is intentionally ephemeral and scoped to the single run, distinct from +the durable per-project lesson store): any subagent spawned via `agent()` can +call a new restricted tool, `note_finding(text)`, whose only effect is to +push `text` onto that run's shared findings list; every *other* subagent +still active in the same run receives the current findings list prepended to +its next tool-round system context, cheaply (no re-spawn, no interruption of +in-flight work — it lands on the subagent's next turn boundary like any other +context update). Findings are ephemeral to the run: they do not automatically +become `lesson` memories (that still requires the normal reviewer pass after +the workflow completes, reading the same `edits.jsonl` ledger as any other +turn) — this feature closes the *within-run* propagation gap, not the +long-term memory gap, which the rest of Phase 7 already covers. + +**Adaptive review frequency**: the per-turn reviewer trigger gains a small +run-length counter — consecutive review passes that produce zero new lessons +(no `remember` calls at all, verified or opinion) increment it; any pass that +produces at least one lesson resets it to zero. Past a small threshold of +consecutive empty passes, the trigger throttles: instead of reviewing every +qualifying turn, it skips a growing number of turns between reviews (simple +backoff, not a fixed schedule), directly reducing the `self-learning`-tagged +token spend the usage dashboard now surfaces. The moment a review does find +something (or the escalation counter fires, or a graduated `check` matches), +the throttle resets to zero immediately — the system is quick to re-engage +and only slow to relax, so a real emerging problem is never delayed by a +prior quiet streak. This is the direct token-cost counterpart to escalation +(escalation raises alarm on repeated *violations*; this lowers cost on +repeated *silence*), and both read the same underlying review-history data, +just in opposite directions. + +**Quality trend metrics on the usage dashboard**: alongside the `self- +learning` token-cost line item already planned, the usage dashboard (Phase +14) accumulates and charts three time series purely from data Phase 7's other +features already produce (no new instrumentation): build/test pass-rate from +outcome-linked verification runs, escalation-note count from the +repeated-violations mechanism, and per-lesson trigger count from the same +counter escalation already reads. A lesson whose trigger count is high but +whose citations never precede a drop in the escalation rate (i.e., it keeps +firing without the underlying pattern ever actually going away) is flagged +as a `forget` candidate in `/learning` — a lesson that keeps getting matched +but never changes behavior is more likely to be miscalibrated or ignored +than genuinely useful, so it's surfaced for human review rather than kept +accumulating silently. + +**Structured lesson bodies**: a `lesson` memory's body gains an optional +`before`/`after` code-snippet pair alongside its existing prose description — +extracted directly from the specific ledger line / diff that triggered the +lesson (the reviewer already has this content in hand when it calls +`remember`; this is a shape change to what it passes, not new data +collection). Rendering (in `/learning`, in the collapsed verdict note, and +implicitly in whatever text gets injected into future system prompts) shows +the snippet pair when present, falling back to prose-only for lessons that +predate this field or for which no single before/after pair meaningfully +captures the point (e.g. a cross-cutting retrospective observation). This +makes a lesson concretely actionable — "change shape X to shape Y" — rather +than purely descriptive, without requiring any new tool or trigger. + +**Lesson-pack export/import**: an opt-in `/lesson export ` command +serializes a project's (or, with a flag, the global store's) `lesson` +entries — full frontmatter including `confidence`/`scope`/`provenance`/ +`check` state, structured bodies, everything — into one portable bundle file, +independent of and without requiring a shared `~/.zesdex/`. `/lesson import +` on another machine (or by a teammate) merges a pack into the local +store using the exact same recall-first dedup and contradiction-detection +path new reviewer-authored lessons already go through (Phase 7's lifecycle +mechanism, reused verbatim — an imported pack is just another source of +candidate lessons, not a privileged bypass), so imported entries land in the +calibration queue exactly like any other non-`/lesson` addition unless they +came from a `/lesson`-authored source originally (in which case they keep +their `confidence: human` standing on import too, since that's a property of +the entry, not of the transport). Export/import is explicitly scoped to +`lesson`/`retrospective` memory types only — it does not touch session +transcripts, `edits.jsonl`, or anything else in the per-session directory. + +## Patterns to Mirror + +| Category | Source | Pattern | +|---|---|---| +| State mutation | `app/runtime/actions/*.rs`, `app/runtime/commands/*.rs` | All state mutation flows through `apply_action`/`apply_slash`; controller/view layers are read-only translators | +| Tool definition | `tool/mod.rs` `Tool` trait | Zero-size struct per tool, `name/description/parameters/run`, registered in one `all_tools()` vec, risk flag is a name-based match in one place | +| Required-argument enforcement | `tool/fs/*.rs` `arg_str` helper | Missing/blank required args fail deterministically before any I/O, via one small shared helper — the pattern zesdex's new `reason` field reuses verbatim | +| Async bridge | `app/runtime/stream/*.rs` | One `mpsc` channel per request; main loop stays synchronous; blocking tool I/O deferred to `std::thread`, not `.await`ed inline | +| Approval gating | `app/harness.rs`, `stream/tools/approval.rs` | Deterministic check → advisory background classifier → per-call intent-aware classifier, each layer independently timeout-bounded and explicit about fail-open vs fail-closed | +| Memory persistence | `model/memory.rs` | Index-of-pointers (`MEMORY.md`) + one frontmatter+body file per entry, atomic rename-over-write, hard-sanitized slugs, only the index (never full bodies) injected into the system prompt | +| IPC framing | `ipc/frame.rs` | 4-byte-BE length prefix + JSON, hard cap enforced before payload allocation | +| Daemon liveness | `ipc/server.rs`, `model/session_lock.rs` | `bind()`/`connect()` success is the liveness oracle, never a PID file; lock files store PID + `kill(pid,0)` liveness check | +| Config layout | `model/settings.rs`, `model/app_config.rs` | Global prefs in one small `config.json`; all per-session config in `settings.json` under a per-session directory | +| Sidecar protocol | `koma_sec_daemon/protocol.py` | Newline-delimited JSON frames, single shared secret handshake, single-threaded serialized dispatch, every error surfaces as a string result, never a crash | + +## Files to Create (Phase 1 scope; later phases add modules incrementally) + +| File | Action | Why | +|---|---|---| +| `Cargo.toml` | CREATE | Package metadata + dependency set per your spec, extracted from koma's `src-agent/Cargo.toml` | +| `src/main.rs` | CREATE | Entry point: terminal setup/teardown, tokio runtime construction, mode dispatch into the event loop | +| `src/app/mod.rs`, `src/app/state.rs`, `src/app/mode.rs` | CREATE | `AppState`, `Mode` enum (Chat + Onboard to start), core runtime state | +| `src/app/runtime/mod.rs`, `event_loop.rs`, `actions.rs`, `commands.rs`, `stream.rs` | CREATE | Synchronous main loop, `Action`/`Command` dispatch, tokio stream bridge | +| `src/app/harness.rs` | CREATE | WC/PC/TAC three-layer approval harness + catastrophic-op guard | +| `src/controller/input.rs`, `src/controller/command.rs` | CREATE | Key→Action translation, slash-command parsing | +| `src/view/mod.rs`, `view/chat.rs`, `view/theme.rs`, `view/markdown.rs`, `view/status.rs` | CREATE | Ratatui rendering: 3-region chat layout + status bar per your spec | +| `src/tool/mod.rs`, `tool/fs.rs`, `tool/search.rs`, `tool/shell.rs`, `tool/shell_filter/*.rs` | CREATE | `Tool` trait + filesystem/search/shell tool set with output filtering; `write`/`edit` schemas include required `reason` from day one | +| `src/tool/git_operator.rs`, `tool/git_worktree.rs`, `tool/git_cred.rs` | CREATE | Git tool set with destructive-op guard | +| `src/dto/chat.rs`, `dto/openrouter.rs` | CREATE | Wire types for the LLM API (OpenRouter-compatible) | +| `src/service/mod.rs`, `service/openrouter.rs` | CREATE | Streaming client, `StreamEvent`, classifier calls | +| `src/model/session.rs`, `model/settings.rs`, `model/store.rs`, `model/app_config.rs`, `model/conversation.rs` | CREATE | Session persistence, per-session settings, config | +| `src/resources.rs`, `src-misc/*.txt` | CREATE | Embedded system/personality/tool-usage/classifier prompts | + +Later phases (see Tasks) add: `src/model/memory.rs` + `src/model/editlog.rs` +(reasoned-edit ledger + `lesson`-typed memory, Phase 7), `src/app/review/mod.rs` +(auto quality-review trigger, Phase 7; extended in-place by 7B/7C/7D/7E — no +new top-level module per sub-phase), `src/app/subagent/*` + +`src/app/workflow/{mod,script,engine}.rs` + `src/tool/workflow.rs` + +`src/view/workflow.rs` + `src/app/mode` workflow-panel overlay (dynamic workflow +orchestration, replaces koma's `task`, Phase 6), `src/app/bgbash/*` (background +jobs), `src/app/mcp/*` (MCP client), `src/service/oauth/*` (OAuth), `src/ipc/*` +(daemon/client split), `src/tool/internet/*` (web fetch/search/download), +`src/app/sec/*` + a `security-sidecar/` Python package (security toolkit), +`src/model/msglog.rs` (SQLite blob archive), `src/app/runtime/shortsend.rs` +(token-efficiency rail). + +## Milestones, Dependency Graph, and Implementation Gates + +This section turns the long phase list below into an executable roadmap. It does +not remove scope; it groups the work so implementation can proceed in stable, +reviewable increments without letting the advanced self-learning surface block +the basic product from becoming usable. + +### Milestone groups + +| Milestone | Phases | Outcome | Can stop here? | +|---|---|---|---| +| **M0 — Plan freeze** | Current document | Scope, risks, dependencies, and acceptance are frozen before code starts | Yes — no code written yet | +| **M1 — Native TUI shell + agent loop** | 1 → 2 → 3 | A usable single-process TUI that can stream model output, run core tools, auto-approve routine work, and still refuse catastrophic operations | Yes — first functional developer preview | +| **M2 — Durable sessions + context economy** | 4 → 5 | Sessions survive restarts, `/resume` works, long conversations stay usable via SQLite archive + short-send shaping | Yes — usable daily-driver baseline without subagents | +| **M3 — Dynamic workflow orchestration** | 6 | `workflow_run` replaces koma's `task`, supports many subagents, progress panel, `parallel`/`pipeline`, and run-scoped `note_finding` | Yes — first multi-agent release | +| **M4 — Self-learning core and growth path** | 7 → 7B → 7C → 7D → 7E | Required edit reasons, audit ledger, background reviewer, lesson lifecycle, escalation, enforced checks, retrospectives, import/export, dashboard data | Yes after any sub-phase; 7 alone is useful | +| **M5 — Background jobs + extensibility + auth** | 8 → 9 → 10 | Long-running shell jobs, MCP tools, and OAuth login are integrated without blocking the TUI | Yes — plugin/provider-ready release | +| **M6 — Detachable multi-session runtime** | 11 | Daemon/client split, attach/detach, snapshot/diff/shadow-state rendering | Yes — multi-session release | +| **M7 — External capability packs** | 12 → 13 | Authorized security toolkit and full internet/search/fetch/download stack land behind their respective opt-in controls | Yes — full agent capability release | +| **M8 — Full UI parity polish** | 14 | Settings, agents editor, MCP browser, usage dashboard, `/learning`, help, rewind, quit confirm, session hub | Yes — full koma-parity UI release | + +### Dependency graph + +```text +M0 plan freeze + ↓ +Phase 1 TUI skeleton + ↓ +Phase 2 streaming agent loop + core tools + ↓ +Phase 3 approval harness + catastrophic-op guard + ↓ +Phase 4 session persistence + ↓ +Phase 5 SQLite archive + short-send + ↓ +Phase 6 workflow orchestration + ├─ requires Phase 2 tool loop + ├─ requires Phase 3 non-interactive risky-tool gating for subagents + └─ provides subagent engine used by Phase 7+ + ↓ +Phase 7 self-learning MVP + ├─ requires Phase 6 detached subagent execution + ├─ requires Phase 4 session directory for edits.jsonl + └─ provides lesson memory + review side-channel + ↓ +Phase 7B outcome/lifecycle/calibration/global tier + └─ requires Phase 7 lesson memory + review trigger + ↓ +Phase 7C escalation/adaptive/batch mining + └─ requires Phase 7B confidence/scope/lifecycle fields + ↓ +Phase 7D graduated checks + shadow mode + ├─ requires Phase 7B lesson frontmatter fields + └─ benefits from Phase 7C repeated-violation counter + ↓ +Phase 7E retrospectives/provenance/user lessons/export/trends + ├─ requires Phase 7B confidence/scope model + ├─ requires Phase 7C counters for trends + ├─ requires Phase 7D checks for graduation UI + └─ rendered fully in Phase 14 dashboard + +Independent after M3/M4 baseline: + Phase 8 background bash jobs + Phase 9 MCP client + Phase 10 OAuth login + +Late structural split: + Phase 11 daemon/client IPC + ├─ requires stable Phase 1 renderer + ├─ requires stable Phase 4 session model + └─ should happen after Phase 8 so background nudges are daemon-aware + +External packs: + Phase 12 security toolkit + ├─ requires Phase 3 catastrophic guard + ├─ requires Phase 4 settings/session storage + └─ benefits from Phase 11 daemon lifecycle, but can be built single-process first + Phase 13 internet module + ├─ requires Phase 2 tool loop + ├─ requires Phase 3 web_download guard adjacency + └─ benefits from Phase 8 background job patterns for sidecar timeout handling + +Final UI parity: + Phase 14 remaining UI modes + ├─ depends on the modes/features it surfaces + └─ can be partially implemented earlier for `/learning` if Phase 7E needs it +``` + +### Implementation gates + +Each gate must pass before the next milestone starts. This is stricter than +"cargo build passes"; it prevents hidden partials from accumulating. + +| Gate | Required before | Criteria | +|---|---|---| +| **G1 — TUI correctness gate** | Start Phase 2 | Phase 1 renders the exact 3-region layout, handles resize, input, scroll basics, and restores terminal state after panic/quit | +| **G2 — Tool-loop correctness gate** | Start Phase 3 | Phase 2 can complete a full mocked tool-use loop, including tool-call parse, tool execution, result append, and second model call | +| **G3 — Safety/speed gate** | Start Phase 4 | Auto/Yolo routine mutations have zero prompts; catastrophic-op guard blocks every named rule even in Yolo; no classifier failure can bypass deterministic guards | +| **G4 — Persistence gate** | Start Phase 5/6 | Sessions create/list/resume cleanly, stale locks are cleared, and no session file write can corrupt partial state on crash | +| **G5 — Context gate** | Start Phase 6 | Long conversation fixture proves SQLite archive and short-send shaping reduce wire payload without destroying transcript fidelity | +| **G6 — Workflow gate** | Start Phase 7 | `workflow_run` is the only delegation surface, `parallel` cap and `pipeline` no-barrier semantics are tested, progress panel survives dismiss/resummon, and `note_finding` is ephemeral/run-scoped | +| **G7 — Self-learning MVP gate** | Start 7B | `write`/`edit` require `reason`, ledger is append-only and exact, one detached reviewer runs per edited turn, and verdicts never enter model-visible history | +| **G8 — Lesson lifecycle gate** | Start 7C | Confidence, stale, scope, calibration, contradiction blocking, and global/project memory injection all have direct tests | +| **G9 — Escalation/adaptive gate** | Start 7D | Repeated-violation escalation and adaptive review throttling both work under synthetic histories and never run reviewers concurrently | +| **G10 — Graduated-check gate** | Start 7E | Shadow-mode trials, graduated checks, override behavior, and de-graduation are tested without making any check an unbreakable blocker | +| **G11 — Learning UI data gate** | Start Phase 14 `/learning` UI | Provenance, retrospectives, import/export, trend metrics, and self-learning token-cost records are all present as model/data APIs before UI work begins | +| **G12 — Daemon split gate** | Start Phase 11 | Single-process runtime is stable through Phases 1–10; renderer state can be snapshotted without borrowing live TUI objects | +| **G13 — External capability gate** | Start Phase 12/13 | Tool gating, settings, background subprocess supervision, and side-channel notes are stable enough that security/internet sidecars cannot destabilize core chat | +| **G14 — Full parity gate** | Finish Phase 14 | Every mode has an Esc/back path, every dashboard reflects live data, and every feature-specific acceptance item below remains green | + +### Recommended implementation order + +For the actual coding pass, the safest order is: + +1. **M1**: Phases 1–3, because they produce the first runnable agent shell and the + always-on catastrophic guard. +2. **M2**: Phase 4, then Phase 5 only if long-session support is needed before + subagents; otherwise Phase 5 can be done immediately after Phase 6. +3. **M3**: Phase 6, because every self-learning feature depends on subagent + execution and workflow orchestration. +4. **M4**: Phase 7 only, stop, test it hard, then decide whether to continue with + 7B–7E immediately or ship the MVP learning loop first. +5. **M5/M6/M7/M8**: Phases 8–14 in order, except `/learning` UI slices from Phase + 14 may be pulled forward after 7E if needed for manual calibration. + +The important policy: **do not start Phase 7B before Phase 7 is stable, and do not +start Phase 11 before the single-process app is already boringly reliable**. + +## Tasks + +### Phase 1 — Skeleton: Cargo.toml, event loop, 3-panel TUI, status bar +- **Action**: Stand up the buildable skeleton exactly matching your explicit + layout spec: Main Execution Log (transcript), Input Prompt (bottom fixed-height + block), Status Bar (`[zesdex] | STATUS: AUTO-APPROVE (UNRESTRICTED) | PROTOCOL: + ZERO-STUBS ACTIVE`). Wire the synchronous main loop + tokio runtime bridge with + no LLM calls yet (a `pong`-style echo tool only), so the rendering/input/event + loop is provably correct before the agentic loop is layered on. +- **Mirror**: `app/runtime/event_loop/mod.rs` tick structure (drain stream events + → drain harness verdicts → input poll with adaptive 8ms/100ms timeout → draw if + dirty); `view/chat/mod.rs` layout_chunks region split. +- **Validate**: `cargo build`, `cargo clippy -- -D warnings`, manual TUI smoke run + (type a line, see it echo into the log region, resize terminal, quit cleanly + restoring the terminal state). + +### Phase 2 — OpenRouter-compatible client + streaming agentic loop +- **Action**: Implement `dto::chat`/`dto::openrouter` wire types, the streaming + SSE client, `StreamEvent` bridge, and `advance_turn`/`process_tools` loop + against a real filesystem/shell tool set (`read, grep, glob, write, edit, + delete, bash, dir_list, cd`). `write`/`edit` already require `reason` per the + Files-to-Create note above — enforced now, even though the review loop that + reads the ledger doesn't land until Phase 7, so the ledger has real data by + the time Phase 7 needs it. No approval gating yet — this phase proves the + model can drive tools end-to-end. +- **Mirror**: `service/openrouter.rs` streaming/classify pattern; `tool/mod.rs` + `Tool` trait and `all_tools()` registration; `MAX_AGENT_STEPS` loop cap. +- **Validate**: Integration test against a mock SSE server; a `write` call + without `reason` is rejected before any file is created; manual run against a + real OpenRouter-compatible endpoint editing a scratch file end-to-end. + +### Phase 3 — Approval harness: Auto/Normal/Plan/Yolo + catastrophic-op guard +- **Action**: Implement WC (deterministic workspace containment), TAC (per-risky- + call classifier, intent-aware, mode-dependent fail-open/fail-closed), and the + `AgentMode` cycle (Auto/Normal/Plan/Yolo). Implement the **catastrophic-op + guard** as a small, explicit, always-on function independent of mode/classifier + state — modeled directly on koma's `git_operator::check_destructive` list + (force-push, `reset --hard`, `clean -f/-d/-x`, force checkout, recursive delete + outside any configured workspace root, credential-file read/exfil patterns) — + and extend it to `bash`/`delete`/`web_download` per your "keep a hard stop on + catastrophic ops" decision. Skip PC (advisory prompt classifier) as lower value + for v1; note it in follow-up. +- **Mirror**: `app/harness.rs` WC/TAC design, `git_operator.rs::check_destructive`. +- **Validate**: Unit tests per guard rule (force-push blocked without confirm, + `reset --hard` blocked, ordinary `git commit`/`write`/`edit` in Auto mode runs + with zero prompts and zero added latency vs. Phase 2 baseline). + +### Phase 4 — Session model + on-disk persistence + `/resume` +- **Action**: `Session`/`Settings`/`AppConfig`/`store` (create/list/rename + sessions), per-session directory layout (`settings.json`, `messages.json`, + `session.lock`, `memory/`, `edits.jsonl`), session picker mode. +- **Mirror**: `model/session.rs`, `model/store.rs`, `model/session_lock.rs` + (PID-liveness lock, never a stale-file oracle). +- **Validate**: Kill `-9` a session mid-write, confirm lock is detected stale and + cleared on next list; `/resume` round-trips a full conversation. + +### Phase 5 — SQLite blob archive + short-send token efficiency (optional-early) +- **Action**: `messages.sqlite` archive (append-only messages/blobs/summary + tables), non-destructive wire-payload compaction rail (`shortsend::shape`) so + budget models stay in-context on long sessions. +- **Mirror**: `model/msglog.rs`, `app/runtime/shortsend.rs` engage-hysteresis math. +- **Validate**: Long synthetic conversation exceeding context window compacts the + wire payload while `messages.json`/transcript stay uncompressed. + +### Phase 6 — Dynamic workflow orchestration engine (replaces koma's `task`) +- **Action**: Build the subagent execution core first (isolated `Conversation` + seed, restricted tool allow-list, non-interactive engine loop with fail-closed + risky-tool gating — this part *is* still a direct mirror of koma's subagent + engine design, just not exposed as a standalone `task` tool). Layer the + `workflow_run(script, args)` tool on top with the four primitives + (`agent`/`parallel`/`pipeline`/`phase`/`log`), a bounded concurrency pool + (default 5, configurable), and the Workflow Progress panel (new `Mode` + overlay reusing `layout_chunks`, live phase→agent→status tree, dismiss/ + re-summon without killing the run). Update the system prompt (`src-misc/ + system-tools.txt` equivalent) to describe `workflow_run` and give the model + concrete guidance on when decomposition is worth it vs. handling inline. + Agent-definition format (frontmatter+markdown, tiered Builtin→Global→Session + merge) is still implemented so `agent()` calls can reference named personas, + and so Phase 7's built-in `quality-reviewer` persona has somewhere to live. +- **Mirror**: `app/subagent/{context,engine,event,spawn}.rs`, `model/ + agent_def/*.rs` for the execution core; `view/chat/subagents.rs` + + `app/mode/mod.rs` overlay-mode pattern for the progress panel's UI shell (no + koma source for the orchestration script layer itself — that's new, specified + above under "Dynamic Workflow Orchestration"). +Also add `note_finding(text)` — a restricted tool available only inside a +`workflow_run` execution context (not part of a standalone subagent's +allow-list) that pushes `text` onto a run-scoped, in-memory, non-persisted +findings broadcast; every other `agent()` thunk still active in the same run +receives the current findings list at its next tool-round boundary. This is +workflow-execution coordination, not lesson persistence, which is why it +lives here rather than in Phase 7 — a finding only becomes a durable +`lesson` if the normal Phase 7 reviewer independently decides to `remember` +it after the run, reading the same `edits.jsonl` ledger as any other turn. +- **Mirror**: `app/subagent/{context,engine,event,spawn}.rs`, `model/ + agent_def/*.rs` for the execution core; `view/chat/subagents.rs` + + `app/mode/mod.rs` overlay-mode pattern for the progress panel's UI shell (no + koma source for the orchestration script layer itself, nor for + `note_finding` — both are new, specified above under "Dynamic Workflow + Orchestration"). +- **Validate**: A `parallel()` workflow with 6 agent thunks confirms the 6th + queues on the concurrency cap; a `pipeline()` workflow with 3 items × 2 stages + confirms item A reaches stage 2 while item B is still on stage 1 (no barrier); + progress panel updates live during a real multi-agent run and correctly + survives Esc-dismiss-and-resummon without losing state; a trivial single-step + request in a manual smoke test is handled inline by the model without + invoking `workflow_run` at all (confirms the "dynamic, not forced" trigger + behavior is actually working, not just documented); inside a `workflow_run` + with two concurrent `agent()` thunks, a `note_finding` call from one is + observably visible to the other's next tool round, and is confirmed absent + from `MEMORY.md` after the run (ephemeral, not persisted). + +### Phase 7 — Reasoned mutations + self-learning quality loop (MVP core) + +Phase 7 was originally one block covering every self-learning idea explored +in planning, but that made it far larger and more tightly coupled than every +other phase in this plan — a violation of the plan's own "each phase is +independently buildable/testable" principle. It is now split into this core +phase plus four additive sub-phases (**7B**–**7E**, deliberately lettered +rather than renumbered so Phases 8–14 below are untouched). **Phase 7 alone +is the complete, working answer to your original request** — required +`reason`, an audit ledger, and an automatic background quality pass; 7B–7E +are refinements layered on top, each independently deferrable if you want a +smaller first slice of the self-learning feature specifically. Anywhere the +Risks/Acceptance tables below say "Phase 7," read it as "the 7/7B/7C/7D/7E +family" unless a sub-phase is called out explicitly. +- **Action**: Add the required `reason` argument to `write`/`edit` (schema + + deterministic rejection before any filesystem call, before any I/O); implement + the append-only `edits.jsonl` ledger with an `origin: main | subagent | + reviewer` tag on every line; add a `lesson` memory kind to `model::memory` + (`name/description/type` only for now — the richer frontmatter fields land + in 7B–7E) plus a recall-before-remember dedup convention so a duplicate + observation is never saved twice; add the built-in `quality-reviewer` + agent-definition persona (read-only + `recall`/`remember` allow-list, no + `write`/`edit`/`bash`, so a review pass can only ever produce a memory note + or nothing); wire the auto-trigger into the event loop's existing + turn-completion point (the same point `Done` already fires) so a non-empty + ledger slice for the just-finished turn spawns exactly one detached-nudge + subagent (Phase 6's engine, detached-nudge mode), capacity-1 queued if + another review is already running, skipped entirely in Plan mode and for + ledger lines whose `origin` is `subagent`/`reviewer` (no recursive + review-the-reviewer chains); add the `SystemNote` side-channel (parallel to + `harness_rx`) so the reviewer's verdict renders as a collapsed note in the + Main Execution Log without ever entering the model-visible transcript. + This is the complete MVP: reasoned mutations, an audit trail, and an + automatic background quality pass that folds observations into memory the + existing `load_memory_index` mechanism already injects every future turn. +- **Mirror**: `tool/fs/write.rs`/`edit.rs` (extend schema, reuse `arg_str`), + `model/memory.rs` (add `kind`, reuse `write_memory`/`read_memory`/ + `slugify`/`atomic_write`), `app/subagent/spawn.rs` detached-nudge fold-back + (built in Phase 6), `app/runtime/event_loop/mod.rs` turn-completion hook, + `harness_rx`'s dedicated-channel pattern for the new `SystemNote` channel. +- **Validate**: A `write`/`edit` call missing `reason` is rejected before any + file is created/modified (unit test, both tools); a turn with 3 edits + produces exactly 3 ledger lines with matching reasons and `origin: main`; a + workflow subagent's own edits are tagged `origin: subagent` and do NOT + double-trigger a nested review; after a turn with edits, exactly one + reviewer subagent runs detached and the Main Execution Log gets exactly one + collapsed verdict note with zero additional turns appended to the + model-visible conversation; a deliberately duplicate lesson (same point, + reworded) is not re-saved because the reviewer's `recall`-first check finds + the existing entry; Plan mode never triggers a reviewer even after a + hypothetical mutation (defence in depth). + +### Phase 7B — Outcome-linked learning, lesson lifecycle, calibration, global tier + +Builds directly on Phase 7's `lesson` kind and reviewer trigger; independently +deferrable from 7C/7D/7E. See "Outcome-linked learning," "Lesson lifecycle," +"Human-in-the-loop calibration," and "Global cross-project lesson tier" in the +digest above for full mechanism detail — summarized here as build items. +- **Action**: Extend `lesson` frontmatter with `confidence: verified | + opinion`, `last_confirmed`, `stale: bool`, and `scope: project | global`; + implement the language-agnostic build/test probe table (settings override → + ordered project-marker detection → skip if none match) as one named, + independently-extensible function, run on a plain thread and handed to the + reviewer alongside the ledger; implement the `pending_lessons.json` + calibration queue (auto-resolve-to-keep after a grace window in Auto/Yolo, + explicit keypress in Normal/Plan) with contradiction detection blocking + auto-save; implement the idle-triggered staleness sweep that flags (never + deletes) old `lesson` entries; implement the `~/.zesdex/memory/` global + store and extend `load_memory_index` to concatenate global-then-project. +- **Mirror**: `shell_filter`'s per-command-special-case pattern generalized + into the build/test probe table; `model/memory.rs` frontmatter extension; + `app/bgbash/*`'s idle-detection reused for the staleness sweep; `app/mode/*` + Builtin→Global→Session tiering pattern reused for the `project`/`global` + lesson split. +- **Validate**: A lesson born from a reproduced build/test failure is tagged + `confidence: verified` and one born from reviewer judgement alone is tagged + `opinion`, visibly distinguished; a deliberately contradictory lesson is + blocked from auto-save and surfaces both entries in the calibration toast; + in Auto mode a pending lesson auto-resolves to `keep` after the grace window + and remains `forget`-able afterward, in Normal mode it waits for an explicit + keypress; an artificially back-dated `last_confirmed` gets flagged + `stale: true` without deletion; a `scope: project` lesson does not leak into + a second project's injected index, while `scope: global` appears in both; + the build/test probe resolves correctly for at least three distinct + project-ecosystem fixtures with no ecosystem hard-coded outside the one + probe table. + +### Phase 7C — Escalation, adaptive review frequency, batch pattern mining + +Builds on 7B's lesson/confidence fields; independently deferrable. See +"Escalation on repeated violations," "Adaptive review frequency," and +"Batch/offline pattern mining" in the digest above. +- **Action**: Implement the repeated-violation counter that escalates to a + non-collapsed attention note (distinct styling from the routine collapsed + `SystemNote`) on the third repeat of a lesson within a configurable window, + and can promote a lesson from `project` to `global` scope when the repeat + spans multiple project directories; implement the consecutive-empty-pass + counter that throttles the per-turn review trigger's frequency and resets + to zero on any new lesson/escalation; implement the idle-triggered batch + pattern-mining pass over full `edits.jsonl` + `git log -p` history, + rate-limited to once per idle window and sharing the same capacity-1 review + queue as the per-turn reviewer. +- **Mirror**: `app/bgbash/*`'s idle trigger as the template for both the + batch-mining pass and the throttle counter's reset condition; ledger + `origin` tagging (Phase 7) reused as the cheap equality check for + repeat-detection scope. +- **Validate**: The same lesson pattern violated a third time within the + window produces a non-collapsed escalation note distinct from routine + verdicts; a synthetic run of N consecutive empty review passes measurably + reduces review frequency, and a single subsequent lesson-producing pass + resets it to reviewing every turn; batch mining never runs concurrently + with a per-turn review (shared capacity-1 queue verified under load). + +### Phase 7D — Lesson graduation into enforced checks (shadow-mode gated) + +Builds on 7B/7C; independently deferrable — a project can run indefinitely on +`lesson`-as-injected-text alone without ever enabling this sub-phase. See +"Lesson graduation into a lint check" and "Shadow-mode enforcement" in the +digest above. +- **Action**: Add the `check` frontmatter field (a small structured rule: + substring/regex against new content, or a path-glob + required-companion- + file rule) and its enforcement inside `write`/`edit` (same deterministic- + before-harness slot as `reason`) for graduated lessons — a failed check + never blocks the write, it returns a naming, addressable/overridable + result; gate graduation behind a mandatory shadow-mode trial window + (`shadow: bool` + `trial_count` frontmatter, same rule-evaluation path, + would-have-fired counting only, nothing surfaced to the model) with a + graduate-vs-demote decision at window close based on fire ratio; restrict + manual graduation/de-graduation to `/learning` (Phase 14) or the two- + reviewer consensus mechanism (Phase 7E) — never reviewer self-promotion. +- **Mirror**: `tool/fs/write.rs`/`edit.rs`'s `reason`-enforcement slot, + reused verbatim for `check` enforcement. +- **Validate**: A `check` candidate spends its full trial window in + `shadow: true` before ever surfacing anything to the model, and correctly + graduates or demotes at window close based on its fire ratio; a graduated + lesson's `check` fires on a violating `write`/`edit` as a named, + addressable result — never a silent or unbreakable block. + +### Phase 7E — Retrospectives, provenance, user-authored lessons, consensus promotion, export/import, trend metrics + +Builds on 7B (confidence/scope) and 7D (consensus gates graduation +promotion); independently deferrable — the MVP loop (Phase 7) and its +lifecycle/escalation refinements (7B/7C) are fully useful without this. See +"Session-end retrospective + per-lesson provenance," "User-authored lessons, +consensus-gated global promotion, token-budget visibility," "Quality trend +metrics on the usage dashboard," "Structured lesson bodies," and +"Lesson-pack export/import" in the digest above. +- **Action**: Add a `provenance` field to every `lesson` (session id + turn + index + ledger line hash, or `"user"` for `/lesson`-authored entries) and + the session-end retrospective trigger (reuses the daemon idle/tombstone + path, Phase 11, and the same reviewer-engine shape at session scope) + writing one `type: retrospective` memory per session, never injected into + the system prompt; add `/lesson ` and `/lesson import ` slash + commands (`confidence: human` for direct authorship, exempt from + calibration/contradiction; `opinion` for bulk-extracted imports, which DO + go through calibration); tighten automatic (non-`/lesson`) `global` + promotion to require two independently-spawned reviewer subagents to agree + before the write; tag every self-learning subagent's token spend with + `origin: self-learning` and surface it as its own usage-dashboard line item + (Phase 14), plus the three trend time series (build/test pass-rate, + escalation count, per-lesson trigger count) and the trigger-without- + improvement `forget`-candidate flag, all derived from data 7A–7D already + produce; extend a `lesson`'s body with an optional structured `before`/ + `after` snippet pulled from the triggering ledger line/diff; add `/lesson + export ` and `/lesson import ` (project or `--global` scope), + the latter routed through the exact same recall-first dedup/contradiction/ + calibration path as any other new lesson, restricted to `lesson`/ + `retrospective` memory types only. +- **Mirror**: `app/subagent/spawn.rs` detached-nudge fold-back (Phase 6, + reused unchanged for retrospective and two-reviewer consensus spawns); + `controller/command.rs` slash-command parsing for `/lesson`; koma's + existing usage-dashboard view, extended for the new cost category and + trend charts. +- **Validate**: Exactly one `type: retrospective` memory is produced per + ended session and is absent from the next session's injected system-prompt + index; `/lesson "text"` creates a `confidence: human` entry immediately + with no calibration toast; `/lesson import` seeds `opinion`-confidence + entries that DO go through calibration; automatic global promotion is + blocked when only one of two spawned reviewers agrees, and proceeds when + both do; the usage dashboard shows a nonzero `self-learning`-tagged token + total and correctly plotted trend charts against a synthetic session + history, with a high-trigger/no-improvement lesson flagged as a `forget` + candidate; a lesson with a structured `before`/`after` pair renders it in + both `/learning` and its injected system-prompt text; `/lesson export` then + `/lesson import` on a second (empty) project reproduces the same lesson set + with matching `confidence`/`scope`/`provenance`, and a near-duplicate + import is caught by the existing dedup path. + +### Phase 8 — Background bash jobs +- **Action**: `run_in_background` bash execution on a plain OS thread, job + registry, `bash_output`/`bash_kill`, idle-triggered completion nudge. +- **Mirror**: `app/bgbash/mod.rs` thread/reader-thread/status-enum design. +- **Validate**: Long-running background command (`sleep 30 && echo done`) polls + correctly mid-run and nudges the model on completion without blocking the UI. + +### Phase 9 — MCP client +- **Action**: `rmcp`-based client, stdio + streamable-HTTP transports, tool + namespacing/dedup, `/mcp` settings UI, config persisted in global config. +- **Mirror**: `app/mcp/mod.rs` connect/dispatch/sync-async-bridge design. +- **Validate**: Connect to a local test MCP stdio server, confirm its tools appear + namespaced in the model's advertised tool list and dispatch correctly. + +### Phase 10 — OAuth login +- **Action**: PKCE authorization-code flow with a loopback callback listener, + token cache with single-flight refresh, `/settings` OAuth submenu. +- **Mirror**: `service/oauth/{pkce,loopback,manager}.rs` design. +- **Validate**: Full browser round-trip against a real OAuth-compatible provider + in a sandboxed test app registration. + +### Phase 11 — Daemon/client IPC split (detachable multi-session) +- **Action**: Unix-socket, length-prefixed JSON-frame IPC; daemon-per-session + process model; snapshot/diff/shadow-state client rendering reusing the Phase 1 + view layer unmodified; CLI daemon subcommands (`status/kill/restart/clean`). +- **Mirror**: `ipc/{frame,server,client,conn}.rs`, `ipc/snapshot/*.rs`, + `app/runtime/client/*.rs`, `app/runtime/event_loop/daemon/*.rs`. +- **Validate**: Detach a running session, confirm the daemon keeps streaming in + the background; reattach and confirm the shadow state matches exactly (no + full-vs-delta divergence) after a burst of token/tool-call activity. + +### Phase 12 — Security toolkit (opt-in, authorized-use only) +- **Action**: `security-sidecar/` Python package mirroring `koma_sec_daemon` + (newline-JSON stdin/stdout protocol, tool registry, sandbox timeouts, tiered + installer, health probe). Rust-side `SecDaemonManager`, `/security` panel + requiring explicit enable + an on-screen authorized-use acknowledgment before + the panel arms, `sec_*` tools harness-exempt per koma's own design (explicit + user-invoked, not silent). Tool roster ported 1:1 from the reference inventory + (http, sqlmap, nuclei, ffuf, dalfox, zap, xss-confirm, z3, sage, rsa, factordb, + lattice, hashcat, hashid, decode, jsdeobf, unmin, sourcemap, wasm, triage, rop, + pwntmpl, remote-session). +- **Mirror**: `src-security/koma_sec_daemon/*` end to end. +- **Validate**: Health probe correctly reports install status per tool; a + representative tool from each category (web/crypto/re/pwn) round-trips a real + call against a local authorized test target (e.g., a deliberately vulnerable + local CTF binary/webapp, not a live third-party system). + +### Phase 13 — Internet module (search/fetch/download + full browser mode) +- **Action**: Lightweight in-process `web_fetch`/`web_search` (reqwest + html2md, + no Python dependency) plus opt-in `internet-sidecar/` Python package + (Playwright-Firefox scraping) for pages that resist the lightweight path, + invoked as a stateless one-shot subprocess per call with a hard timeout and + fallback to the lightweight path on any failure. `web_download` carries the + 500 MiB cap and is in the catastrophic-op-guard-adjacent risky set (still no + approval prompt in Auto/Yolo, but still subject to the destructive-target guard + if it targets a path outside the workspace). +- **Mirror**: `tool/internet/*.rs`, `src-internet/scrapion_agent/*`. +- **Validate**: Fetch a JS-heavy page that fails the lightweight path, confirm + automatic fallback to the browser sidecar succeeds and returns clean markdown. + +### Phase 14 — Remaining UI surface (settings, agents editor, MCP browse, usage +dashboard, help, effort picker, message rewind, quit confirm, session hub, +`/learning` dashboard) +- **Action**: Fill out the remaining `Mode` variants and their views/controllers + so the app matches koma's full UI surface, not just chat. Add the new + `/learning` dashboard mode (no koma equivalent — new for zesdex's Phase 7 + feature set): lists every `lesson` memory (project + global, clearly + separated), sortable by trigger count / `confidence` / `stale` flag / recency, + with inline `forget`, manual `project → global` promotion, and manual + lesson-graduation/de-graduation actions reusing the same primitives Phase 7 + built; a per-lesson detail view resolves its `provenance` back to the + originating session/turn and renders its structured `before`/`after` + snippet when present; a shadow-mode lesson's row shows its live + would-have-fired counter and trial-window progress; a separate list browses + `type: retrospective` memories by session; `/lesson export`/`import` are + also reachable as dashboard actions (not just slash commands), for + discoverability; the existing usage dashboard mode gains a `self-learning`- + tagged line item alongside the main agent's own token spend plus the three + quality-trend charts (build/test pass-rate, escalation count, per-lesson + trigger count) and the trigger-without-improvement `forget`-candidate + flagging (Phase 7 already tags/produces all of this data — this phase only + needs to render it). +- **Mirror**: `app/mode/*.rs`, `view/*.rs` per-mode files (`/learning` has no + koma source — built directly on Phase 7's `model::memory` extensions and the + existing `session_hub`-style list-mode view pattern for the layout shell); + koma's existing usage-dashboard view (extended, not replaced, for the new + cost category and trend charts). +- **Validate**: Manual pass through every mode transition documented in the + reference's mode state machine; no dead-end mode with no Esc/back path; + `/learning` correctly reflects a lesson's live `confidence`/`stale`/`scope` + state and a `forget` performed from the dashboard is picked up by the next + system-prompt rebuild; a lesson's provenance link correctly resolves to its + originating turn; a shadow-mode lesson's dashboard row updates live as its + trial counter increments; the usage dashboard's `self-learning` line item + and trend charts sum/plot correctly against a synthetic session history. + +## Validation (run after every phase, not just at the end) + +```sh +cargo build --workspace +cargo clippy --workspace -- -D warnings +cargo test --workspace +cargo run -- # manual smoke: start, drive one full tool-use turn, quit cleanly +``` + +## Risks + +| Risk | Likelihood | Mitigation | +|---|---|---| +| "Zero comments, self-documenting" collides with genuinely non-obvious logic (e.g. cache-split marker placement, hysteresis thresholds in short-send) | Medium | Push complexity into well-named functions/types instead of comments; where a magic constant needs justification, name the constant itself descriptively rather than adding a comment | +| Security toolkit scope creep into unauthorized-use tooling | Low, given your stated authorized-use context | Phase 12 ships behind an explicit enable + on-screen acknowledgment, and the plan does not add any capability beyond the reference's own curated roster | +| "Full 1:1 clone" is a multi-week effort disguised as one `/plan` | High if treated as one PR | Plan is explicitly phased; each phase is independently buildable/testable and merges before the next starts — flag now if you want a smaller first slice | +| IPC/daemon split (Phase 11) is the highest-complexity phase and has the most failure modes (stale sockets, snapshot divergence) | Medium | Scheduled late, after the single-process TUI is fully proven, so the daemon split only has to preserve already-working behavior, not debug it simultaneously | +| Catastrophic-op guard list drifts out of sync as new risky tools are added later | Medium | Guard membership is a single named function/table (mirroring `tool_is_risky`), not scattered checks — Phase 3 establishes this as the one place to extend | +| Model over-invokes or under-invokes `workflow_run` (fan-out for trivial asks, or never fans out for genuinely decomposable work) since the trigger is judgement-based, not rule-based | Medium | Phase 6's validate step includes an explicit "trivial request stays inline" smoke test; system-prompt guidance is iterated on with real usage rather than assumed correct on first write | +| `workflow_run`'s embedded script surface (`agent`/`parallel`/`pipeline`) becomes a de facto second scripting language to maintain if scope creeps beyond the four primitives | Medium | Primitives are fixed at four for v1 (`agent`, `parallel`, `pipeline`, `phase`+`log` as cosmetic); no general control flow, no arbitrary host-function calls beyond spawning agents | +| Reviewer subagent produces low-signal or wrong "lessons" (hallucinated conventions, misjudged reasons) that then get injected into every future system prompt, actively degrading quality instead of improving it | Medium | Reviewer is read-only (can't touch files, only `remember`), every `remember` requires an explicit `recall`-first dedup pass, memory entries stay human-editable/deletable (`forget`) at any time, and the plan's acceptance bar requires the verdict note to be visible in-session so a bad lesson is easy to spot and remove, not silently compounding | +| Reasoned-edit ledger (`edits.jsonl`) grows unbounded over a long-lived project, and the reviewer queue (capacity 1) backs up during a burst of many small edits | Low for v1 scope | Ledger rotation/compaction is explicitly out of scope for v1 (flag as a fast-follow); the capacity-1 reviewer queue is a deliberate backpressure choice — reviews are best-effort and coalescing/skipping under load is acceptable, unlike the ledger itself which must never drop a line | +| A bad `global`-scope lesson pollutes every future project's system prompt, unlike a project-scope mistake which stays contained | Medium | Promotion to `global` is gated behind either explicit human calibration or the multi-project repeated-violation counter — never a single reviewer's own judgement — and `/learning` (Phase 14) gives an always-available manual `forget` path for any global lesson | +| Outcome-linked build/test verification adds real wall-clock cost to every file-touching turn, and a slow/flaky project test suite could make the reviewer trigger feel laggy or noisy | Medium | Verification runs on its own thread off the critical path (the user's turn is never blocked waiting on it), is capped at a short timeout, and degrades gracefully to reasons+diff-only review on timeout/failure/misconfiguration rather than blocking the reviewer entirely | +| Batch/offline pattern mining and the per-turn reviewer could in principle compound into runaway subagent spawning on a very active session | Low | Batch mining is explicitly rate-limited to at most once per idle window and shares the same capacity-1 review queue as the per-turn reviewer, so the two can never run concurrently | +| A graduated lesson's `check` rule is a blunt substring/glob match, not real language-aware parsing — it can misfire on a legitimate edit that superficially matches the pattern | Medium | A failed check never blocks the write outright; it returns a naming result the model can address or explicitly override via `reason`, and de-graduation is always available from `/learning` if a rule proves too blunt in practice | +| Two-reviewer consensus for automatic `global` promotion doubles the token cost of that specific path, and both reviewers could still share the same blind spot if spawned from an identical prompt | Low | The cost only applies to the rare `global`-promotion path, not routine per-turn review; the two reviewers are spawned independently (fresh context each) specifically so correlated blind spots are less likely than a single pass, though not impossible — flagged as a known limitation, not a guarantee | +| `/lesson import` bulk-extracting from a large convention document could flood the calibration queue with many pending entries at once | Low | Imported entries land at `confidence: opinion` and go through the same calibration toast as any reviewer-authored lesson — no special bypass — so the existing keep/discard/auto-resolve mechanics apply unchanged, just at higher volume for that one command | +| Shadow-mode trial window adds a delay before a genuinely useful graduated check becomes protective, during which the same real mistake could recur uncaught | Low | The trial window is a small fixed count by design (not open-ended), and the underlying `lesson` itself is still actively injected into the system prompt as `opinion`/`verified` text throughout the trial — shadow mode only gates the *hard enforcement* layer, not the softer prompt-injection signal, so protection is never fully absent during the trial | +| `note_finding`'s run-scoped broadcast could be misused as a general side-channel for workflow subagents to coordinate beyond its intended "share a quality finding" purpose, growing into an unbounded ad hoc messaging primitive | Medium | Scoped to exactly one tool, one direction (broadcast, not point-to-point), ephemeral (never persisted, dies with the run), and delivered only at existing tool-round boundaries (no new scheduling primitive) — the same "fixed primitives, no scope creep" discipline already applied to `workflow_run`'s four script primitives | +| Adaptive throttling could under-review a genuinely quiet-but-still-risky stretch of edits if the backoff schedule grows too aggressive | Low | Backoff resets to zero immediately on any new lesson/escalation/check-match, and the cap on how far it can back off is a fixed named constant (same "one place to extend" discipline as the catastrophic-op guard), so the maximum possible under-review window is always a known, bounded quantity, not unbounded drift | +| Quality trend metrics could be over-interpreted as proof the self-learning loop "works" when the underlying signals (pass-rate, escalation count) can move for unrelated reasons (e.g. the project itself just got harder) | Low | Charts are presented as raw trend data for human interpretation in `/learning`, not as an automated pass/fail verdict on the feature itself — the `forget`-candidate flag is a nudge for human review, not an automatic deletion | +| Lesson-pack export could leak project-specific naming/paths embedded in a `before`/`after` snippet when shared with a team or another machine, unlike prose-only lessons | Low | Export is opt-in and explicit per invocation (never automatic), scoped to `lesson`/`retrospective` types only, and the user reviews the exported file (plain readable frontmatter+markdown, same format as any other memory file) before sharing it — same trust model as sharing any other project file | + +## Acceptance +- [ ] Phase 1 skeleton builds, runs, renders the exact 3-region layout + status + bar text specified, and restores the terminal cleanly on quit +- [ ] `Cargo.toml` matches the specified name/authors/dependency set exactly +- [ ] No `unimplemented!()`, `todo!()`, `/* implement later */`, or equivalent + placeholder in any file belonging to a "done" phase +- [ ] No `//`, `///`, or `/* */` comments anywhere in `src/` +- [ ] No `#![allow(dead_code)]` / `#![allow(unused_variables)]` or per-item + `#[allow(...)]` suppressions +- [ ] Auto/Yolo mode performs routine writes/edits/shell/git with zero approval + prompts and zero added latency +- [ ] Catastrophic-op guard blocks its named operation list even in Yolo mode, + verified by unit test per rule +- [ ] Security toolkit is inert until explicitly enabled, and the enable path + surfaces an authorized-use acknowledgment +- [ ] `workflow_run` is the sole subagent-delegation surface (no separate + single-agent `task` tool coexists with it) +- [ ] Workflow Progress panel auto-appears on `workflow_run` start, is + dismissible without killing the run, and is re-summonable +- [ ] `pipeline()` demonstrably has no inter-stage barrier and `parallel()` + demonstrably respects the concurrency cap, each covered by its own test +- [ ] `write`/`edit` calls without a non-empty `reason` are rejected before any + filesystem mutation, verified by unit test for both tools +- [ ] Every accepted `write`/`edit` produces exactly one `edits.jsonl` line with + a matching `path`/`reason`/`origin` +- [ ] Exactly one detached reviewer subagent runs per turn that touched files + (never zero when edits happened outside Plan mode, never more than one + concurrently), and its verdict appears as a collapsed system note that is + never appended to the model-visible transcript +- [ ] A reviewer's `remember(type="lesson")` calls are demonstrably dedup'd + against existing lessons via a `recall`-first check (no duplicate memory + entries from repeated similar observations) +- [ ] Lessons are labeled `confidence: verified` only when backed by a + reproduced build/test failure, `opinion` otherwise, and the two are + visibly distinguished everywhere lessons are listed +- [ ] A contradictory new lesson blocks auto-save and surfaces both entries in + the calibration toast instead of overwriting silently +- [ ] Pending lessons auto-resolve to `keep` in Auto/Yolo after a grace window + (staying visible/reversible) and require an explicit keypress in Normal + mode +- [ ] A `scope: project` lesson never leaks into a different project's + injected memory index; a `scope: global` lesson appears in every project +- [ ] A lesson pattern repeated a third time within the configured window + produces a non-collapsed escalation note distinct from routine verdicts +- [ ] `/learning` dashboard (Phase 14) lists every lesson with live + confidence/stale/scope state and supports `forget` + manual promotion +- [ ] The build/test verification probe resolves correctly for at least three + distinct project-ecosystem fixtures with no ecosystem hard-coded outside + the one named probe table (language-agnostic requirement) +- [ ] A graduated lesson's `check` fires on a violating `write`/`edit` as a + named, addressable/overridable result — never a silent or unbreakable + block +- [ ] Every `lesson` resolves its `provenance` back to a real session/turn (or + `"user"` for a `/lesson`-authored entry), verified from `/learning` +- [ ] Exactly one `type: retrospective` memory is produced per ended session + and is absent from the next session's injected system-prompt index +- [ ] `/lesson ` creates a `confidence: human` entry with no calibration + delay; `/lesson import ` seeds `opinion`-confidence entries that DO + go through calibration +- [ ] Automatic (non-`/lesson`) promotion to `global` scope requires two + independently-spawned reviewers to agree, verified by a test where only + one agrees and the promotion is blocked +- [ ] The usage dashboard (Phase 14) shows a distinct `self-learning` token- + cost line item, separate from the main agent's own spend +- [ ] A `check` candidate spends its full trial window in `shadow: true` + before ever blocking/naming anything to the model, and correctly + graduates or demotes at window close based on its fire ratio +- [ ] `note_finding` calls inside a `workflow_run` are observable by sibling + `agent()` thunks in the same run and are confirmed absent from + `MEMORY.md` afterward (ephemeral, run-scoped only) +- [ ] Consecutive empty review passes measurably reduce review frequency, and + a single subsequent lesson/escalation/check-match resets it to zero +- [ ] Usage dashboard trend charts (pass-rate, escalation count, per-lesson + trigger count) plot correctly against a synthetic session history, and + a high-trigger/no-improvement lesson is flagged as a `forget` candidate +- [ ] A lesson with a structured `before`/`after` snippet renders it in both + `/learning` and its injected system-prompt text +- [ ] `/lesson export` followed by `/lesson import` on a separate project + reproduces the same lesson set with matching confidence/scope/ + provenance, and a near-duplicate import is caught by the existing dedup + path +- [ ] Each phase's own validation commands pass before the next phase starts + +**WAITING FOR CONFIRMATION**: Proceed with this plan? (yes/no/modify — you can +also ask to start at a specific phase, e.g. "just do Phase 1-3 for now")