diff --git a/.claude/plans/zesdex.plan.md b/.claude/plans/zesdex.plan.md deleted file mode 100644 index 568cac9..0000000 --- a/.claude/plans/zesdex.plan.md +++ /dev/null @@ -1,1401 +0,0 @@ -# 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") diff --git a/README.md b/README.md new file mode 100644 index 0000000..df4090b --- /dev/null +++ b/README.md @@ -0,0 +1,181 @@ +# Zesdex + +Autonomous AI coding and security agent running in a terminal-based TUI environment. + +Zesdex is a Rust-powered AI assistant that operates directly in your terminal via a rich TUI interface. It combines large language model intelligence with a comprehensive set of tools to explore, understand, and modify codebases autonomously. + +## Features + +- **TUI Interface** — Full-screen terminal UI with chat panel, input bar, and status bar built with ratatui and crossterm. +- **Multi-Agent Modes** — Auto (full autonomy), Normal (review risky ops), Plan (no mutations), Yolo (unrestricted). +- **Rich Tool System** — 20+ built-in tools for file operations, searching, bash execution, git operations, web access, memory management, and workflow orchestration. +- **Daemon Architecture** — Run as a background daemon with client attach/detach via Unix domain sockets. +- **IPC Protocol** — Bidirectional state synchronization between daemon and client processes. +- **MCP Support** — Model Context Protocol integration for connecting to external AI servers. +- **Self-Learning** — Persistent memory system that stores lessons, references, and project knowledge. +- **Workflow Engine** — Orchestrate complex multi-step tasks with parallel sub-agents, pipelines, and phased execution. +- **Security Guardrails** — Catastrophic operation detection, credential exfiltration monitoring, graduated safety checks. +- **Security Sidecar** — Python-based security analysis daemon for vulnerability scanning. +- **Provider Agnostic** — Configurable AI model providers with dynamic model selection. +- **Session Management** — Multiple sessions with history, rewind, and transcript persistence. + +## Architecture + +``` +src/ +├── main.rs # Entry point, single/daemon/attach modes +├── app/ # Application core +│ ├── state/ # State management (AppStateRest, types, misc) +│ ├── runtime/ # Action dispatch and event loop +│ ├── mode/ # Agent mode definitions (Auto/Normal/Plan/Yolo) +│ ├── harness.rs # Tool harness for agent execution +│ ├── workflow/ # Workflow engine (script, engine) +│ ├── mcp/ # Model Context Protocol manager +│ ├── sec/ # Security daemon integration +│ ├── subagent/ # Sub-agent orchestration +│ ├── bgbash/ # Background bash job management +│ └── review/ # Self-review quality system +├── controller/ # Input handling and command dispatch +│ ├── input.rs # Key event processing +│ └── command.rs # Slash command parser +├── dto/ # Data transfer objects +│ ├── chat/ # Message types +│ └── provider/ # AI provider request/response types +├── ipc/ # Inter-process communication +│ ├── protocol.rs # Message protocol definition +│ ├── server.rs # Unix socket server +│ ├── client.rs # Unix socket client +│ ├── conn.rs # Connection framing +│ ├── frame.rs # Frame encoding/decoding +│ ├── snapshot.rs # State snapshot +│ └── diff.rs # State diffing +├── model/ # Data models +│ ├── store.rs # File-based storage +│ ├── session.rs # Session management +│ ├── settings.rs # User settings +│ ├── app_config.rs # Provider configuration +│ ├── memory.rs # Persistent memory +│ ├── editlog.rs # Edit history log +│ ├── msglog/ # Message log persistence +│ ├── agent_def/ # Agent definitions +│ └── session_lock.rs # Session locking +├── security/ # Security installation utilities +│ └── install.rs # Sidecar binary management +├── service/ # External service integrations +│ ├── provider.rs # AI provider abstraction +│ └── oauth/ # OAuth authentication +├── tool/ # Tool implementations +│ ├── fs/ # read, write, edit, delete +│ ├── search.rs # grep, glob +│ ├── shell.rs # bash execution +│ ├── bash_tools.rs # bash_output, bash_kill +│ ├── git_operator.rs # git operations +│ ├── git_worktree.rs # git worktree management +│ ├── git_cred.rs # git credential management +│ ├── internet/ # fetch, download, search +│ ├── memory/ # remember, forget, recall +│ ├── plan.rs # plan_enter, plan_ready +│ ├── seqthink.rs # Sequential thinking +│ ├── workflow.rs # workflow_run, note_finding +│ ├── utility/ # cd, dir_list, dir_cache_update, pong, todowrite +│ └── shell_filter/ # Shell output filtering +├── view/ # TUI rendering +│ ├── chat.rs # Chat transcript panel +│ ├── markdown.rs # Markdown rendering +│ ├── status.rs # Status bar +│ ├── theme.rs # Color scheme +│ └── workflow.rs # Workflow visualization +└── resources.rs # Embedded resources (help text, prompts) +``` + +## Usage + +```bash +# Run in single-process mode (default) +zesdex + +# Run as a background daemon +zesdex --daemon + +# Attach to a running daemon session +zesdex --attach + +# Set log level +RUST_LOG=debug zesdex +``` + +### Key Bindings + +| Binding | Action | +|---------|--------| +| `Ctrl+Q` | Quit | +| `Ctrl+H` | Help overlay | +| `Ctrl+P` | Settings overlay | +| `Ctrl+A` | Cycle agent mode | +| `Ctrl+B` | Bash panel | +| `Ctrl+S` | Session hub | +| `Ctrl+T` | Task list | +| `Ctrl+W` | Workflow view | +| `Ctrl+K` | Key input mode | +| `Esc` | Cancel / back | +| `Tab` | Autocomplete | +| `↑/↓` | History / navigation | + +### Slash Commands + +| Command | Description | +|---------|-------------| +| `/help` | Show help | +| `/clear` | Clear transcript | +| `/model` | Select AI model provider | +| `/exit` | Exit application | +| `/settings` | Open settings | +| `/session` | Session management | + +## Agent Modes + +- **Auto** — Full autonomy. The agent can read, write, edit files, run bash commands, and execute git operations without confirmation. +- **Normal** — Risky operations (write, delete, edit, bash, destructive git) require manual approval. +- **Plan** — Planning mode. The agent can analyze and propose changes but cannot execute mutations. +- **Yolo** — Unrestricted mode. Full autonomy with no confirmation prompts. + +## Security + +Zesdex includes multiple layers of security: + +- **Catastrophic Guard** — Detects and blocks destructive operations (rm -rf, force push, credential exfiltration) across all modes. +- **Graduated Checks** — Content-aware pattern matching for common danger zones (API keys, passwords, git credentials). +- **Security Sidecar** — Optional Python daemon for deep vulnerability scanning. +- **Session Locking** — Prevents multiple processes from operating on the same session directory. +- **Workspace Isolation** — All file operations are validated against workspace roots. + +## Installation + +### Prerequisites + +- Rust 2021 edition toolchain +- (Optional) Python 3 for the security sidecar + +### Build from source + +```bash +git clone +cd zesdex +cargo build --release +./target/release/zesdex +``` + +### Security sidecar (optional) + +```bash +pip install -r security-sidecar/requirements.txt +``` + +## Configuration + +Configuration is stored in `~/.config/zesdex/` (or platform equivalent). Key files: + +- `config.yaml` — Provider settings, default model, temperature, max tokens +- `app_config.yaml` — AI provider definitions (name, URL, auth type) +- `memory/` — Persistent lesson and reference storage +- `sessions/` — Per-session transcripts and activity logs diff --git a/src/app/harness.rs b/src/app/harness.rs index 484e6f8..612e32c 100644 --- a/src/app/harness.rs +++ b/src/app/harness.rs @@ -2,7 +2,6 @@ pub enum Verdict { Allow, Block(String), - Escalate, } pub struct Harness; @@ -80,7 +79,6 @@ mod tests { "block" => Some(Verdict::Block( v.get("reason").and_then(|r| r.as_str()).unwrap_or("blocked").to_string() )), - "escalate" => Some(Verdict::Escalate), _ => None, }; } @@ -153,12 +151,6 @@ mod tests { assert_eq!(v, Some(Verdict::Block("dangerous operation".to_string()))); } - #[test] - fn test_parse_verdict_json_escalate() { - let v = parse_verdict(r#"{"verdict": "escalate"}"#); - assert_eq!(v, Some(Verdict::Escalate)); - } - #[test] fn test_parse_verdict_text_allow() { let v = parse_verdict("Verdict: Allow"); diff --git a/src/app/mode/editor.rs b/src/app/mode/editor.rs index bb1a1d1..cf34998 100644 --- a/src/app/mode/editor.rs +++ b/src/app/mode/editor.rs @@ -8,16 +8,6 @@ pub struct EditorState { pub undo_stack: Vec>, pub cursor_line: usize, pub cursor_col: usize, - pub scroll_offset: usize, - pub active: bool, - pub mode: EditorMode, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EditorMode { - Normal, - Insert, - Visual, } impl Default for EditorState { @@ -28,9 +18,6 @@ impl Default for EditorState { undo_stack: Vec::new(), cursor_line: 0, cursor_col: 0, - scroll_offset: 0, - active: false, - mode: EditorMode::Normal, } } } @@ -41,7 +28,6 @@ impl EditorState { EditorState { path, content, - active: true, ..Default::default() } } @@ -160,29 +146,6 @@ impl EditorState { pub fn as_string(&self) -> String { self.content.join("\n") } - - pub fn close(&mut self) { - self.active = false; - } - - pub fn toggle_mode(&mut self) { - self.mode = match self.mode { - EditorMode::Normal => EditorMode::Insert, - EditorMode::Insert => EditorMode::Normal, - EditorMode::Visual => EditorMode::Normal, - }; - } -} - -#[derive(Clone)] -pub struct AppEditorState { - pub editor: Option, -} - -impl AppEditorState { - pub fn new() -> Self { - AppEditorState { editor: None } - } } pub fn handle_editor_input(state: &mut AppStateRest, text: String) { diff --git a/src/app/mode/mcp.rs b/src/app/mode/mcp.rs index 2b3755f..8a61fa4 100644 --- a/src/app/mode/mcp.rs +++ b/src/app/mode/mcp.rs @@ -1,17 +1,6 @@ use crate::app::state::rest::AppStateRest; -use crate::app::state::types::Overlay; pub fn connect_mcp(state: &mut AppStateRest, server_name: &str) { let _ = server_name; state.dirty = true; } - -pub fn disconnect_mcp(state: &mut AppStateRest, server_name: &str) { - let _ = server_name; - state.dirty = true; -} - -pub fn handle_mcp_dismiss(state: &mut AppStateRest) { - state.misc.overlay = Overlay::None; - state.dirty = true; -} diff --git a/src/app/runtime/actions/mod.rs b/src/app/runtime/actions/mod.rs index 00a290d..cd99b4f 100644 --- a/src/app/runtime/actions/mod.rs +++ b/src/app/runtime/actions/mod.rs @@ -275,6 +275,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) { state.dirty = true; } Action::Tick => { + state.misc.tick_count = state.misc.tick_count.wrapping_add(1); let now_ms = chrono::Utc::now().timestamp_millis(); state.misc.drain_expired_toasts(now_ms); crate::app::review::maybe_run_staleness_sweep(state); @@ -693,12 +694,6 @@ fn run_agent_turn( Err(e) => (e.to_string(), true, false), }, Verdict::Block(reason) => (format!("Blocked: {}", reason), true, false), - Verdict::Escalate => ( - "Tool requires approval. Provide explicit approval." - .to_string(), - true, - false, - ), }; if is_edit { diff --git a/src/app/state/misc.rs b/src/app/state/misc.rs index 90902a3..c896dec 100644 --- a/src/app/state/misc.rs +++ b/src/app/state/misc.rs @@ -236,6 +236,7 @@ pub struct MiscState { pub selected_index: usize, pub editor: Option, pub api_connected: bool, + pub tick_count: u64, } impl MiscState { @@ -252,6 +253,7 @@ impl MiscState { selected_index: 0, editor: None, api_connected: false, + tick_count: 0, } } diff --git a/src/view/chat.rs b/src/view/chat.rs index 66aed55..96c6dd3 100644 --- a/src/view/chat.rs +++ b/src/view/chat.rs @@ -83,10 +83,12 @@ pub fn draw_chat(frame: &mut Frame, area: Rect, state: &crate::app::state::rest: display_lines.push(Line::from(Span::raw(""))); } - if state.misc.thinking { + if state.turn_in_flight() { + let spinner_frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + let frame = spinner_frames[(state.misc.tick_count as usize / 2) % spinner_frames.len()]; display_lines.push(Line::from(vec![ Span::styled(" AI ", Style::default().fg(Theme::BG).bg(Theme::ROLE_ASSISTANT).add_modifier(Modifier::BOLD)), - Span::styled(" Thinking...", Style::default().fg(Theme::DIM)), + Span::styled(format!(" {} Generating...", frame), Style::default().fg(Theme::DIM)), ])); display_lines.push(Line::from(Span::raw(""))); } diff --git a/src/view/status.rs b/src/view/status.rs index 731dd32..e6eaa76 100644 --- a/src/view/status.rs +++ b/src/view/status.rs @@ -10,12 +10,14 @@ pub fn draw_status_bar(frame: &mut Frame, area: Rect, state: &crate::app::state: // PROG → turn is in flight // READY → connected and ready // NOAPI → disconnected + let spinner_frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; let (agent_status, conn_color) = if state.turn_in_flight() { - ("PROG", Theme::MODE_YOLO) + let frame = spinner_frames[(state.misc.tick_count as usize / 2) % spinner_frames.len()]; + (format!("{} PROG", frame), Theme::MODE_YOLO) } else if state.misc.api_connected { - ("READY", Theme::MODE_AUTO) + ("READY".to_string(), Theme::MODE_AUTO) } else { - ("NOAPI", Theme::DIM) + ("NOAPI".to_string(), Theme::DIM) }; let status = Span::styled( format!(" {} ", agent_status),