| 1 | # Codewhale agent guidance |
| 2 | |
| 3 | Keep this file durable. Derive changing release, provider, branch, and flake |
| 4 | state from the repository, tests, CI, and current issue tracker rather than from |
| 5 | instructions or memory. The nearest scoped `AGENTS.md` adds path-specific rules. |
| 6 | |
| 7 | ## The ponytail method |
| 8 | |
| 9 | From [dietrichgebert/ponytail](https://github.com/dietrichgebert/ponytail) — |
| 10 | "the laziest senior dev in the room." *He says nothing. He writes one line. It |
| 11 | works.* The best code is the code you never wrote. |
| 12 | |
| 13 | Before writing code, walk the decision ladder in order and stop at the first |
| 14 | rung that answers: |
| 15 | |
| 16 | 1. **Does this need to exist?** → Skip it. |
| 17 | 2. **Already in this codebase?** → Reuse it. |
| 18 | 3. **Stdlib does it?** → Use it. |
| 19 | 4. **Native platform feature?** → Use it. |
| 20 | 5. **Installed dependency?** → Use it. |
| 21 | 6. **One line?** → One line. |
| 22 | 7. **Only then:** the minimum that works. |
| 23 | |
| 24 | The ladder runs *after* understanding the problem. Lazy about solutions, never |
| 25 | about reading the code first — a short diff written without reading the call |
| 26 | sites is not ponytail, it is a guess. |
| 27 | |
| 28 | **Never cut, at any rung:** trust-boundary validation, data-loss handling, |
| 29 | security, accessibility. Brevity is not a reason to drop a guard. |
| 30 | |
| 31 | Rung 2 is the one this repository keeps failing. The `model_*` / `*_config` / |
| 32 | `provider_*` grep rule below is rung 2 with a name; so is "one turn loop, one |
| 33 | base prompt". Two more corollaries earned here: |
| 34 | |
| 35 | - **An abstraction must delete caller code.** If adopting it is pure |
| 36 | obligation — required methods, no default bodies that do work — it gets |
| 37 | built, adopted once, and abandoned. |
| 38 | - **Migrate the last consumer, or do not start.** Framework, one caller, |
| 39 | ticket the rest, silence the warning: that ships two systems and a comment |
| 40 | that is no longer true. If the migration will not fit, narrow the slice — |
| 41 | never the adoption. The standing `#[allow(dead_code)]` count is the running |
| 42 | receipt; `scripts/check-dead-code-budget.py` prints it. |
| 43 | |
| 44 | ## Working rules |
| 45 | |
| 46 | - Inspect status and existing consumers before editing. Preserve unrelated, |
| 47 | dirty, and untracked work. |
| 48 | - Before adding a module named `model_*`, `*_config`, `provider_*`, or |
| 49 | anything that "bridges", "mirrors", or "stages" an existing thing, grep |
| 50 | for the existing thing and edit it. A new layer must name the predecessor |
| 51 | it replaces in the module doc; otherwise edit the original. |
| 52 | - Prefer the simplest implementation that preserves observable contracts. A |
| 53 | rewrite is acceptable when justified by product intent and observed behavior, |
| 54 | not as a shortcut around understanding existing code. |
| 55 | - Search for behavior and symbols before reviving work from an old branch. If a |
| 56 | lane is obsolete, preserve its intent and evidence rather than merging stale |
| 57 | code mechanically. |
| 58 | - A small coherent change may be committed directly to `main` when that checkout |
| 59 | is current, clean, and owns the affected files. Default to the checkout that |
| 60 | already exists: when several agents share it, partition by file, stage only |
| 61 | the paths your slice touched, and retry a commit that fails on `index.lock`. |
| 62 | A fresh worktree is for conflicting, dirty, stale, or independent lanes |
| 63 | (see `cw-land`), not for parallel agents on the same lane. Local commit |
| 64 | permission never implies push, merge, tag, release, or deploy permission. |
| 65 | - When the task is local-only, stay fully offline: no browsing, GitHub or remote |
| 66 | Git operations, downloads, dependency installation, provider calls, or |
| 67 | source/diff transmission. Record the missing external receipt and keep working |
| 68 | locally. |
| 69 | - Public name is **Codewhale**. Compatibility identifiers such as `CodeWhale`, |
| 70 | `codew`, protocol names, and storage keys change only through an explicit |
| 71 | migration. |
| 72 | - Keep providers and models first-class and provider-neutral. |
| 73 | - Never rewrite published history, retag a release, force-push a shared ref, or |
| 74 | publish without explicit authorization. Preserve human contributor credit. |
| 75 | - **Model-visible means logged.** Anything that reaches a model request must be |
| 76 | reconstructable from the session log, and a new model-visible input needs a |
| 77 | session event. Live presentation and the persisted record must agree; when they |
| 78 | disagree the record is right. |
| 79 | - **Misconfiguration fails loud**, at load when it is self-contained, otherwise |
| 80 | at the earliest point it can be resolved. Never silently skip a missing |
| 81 | referent. |
| 82 | - **Write down what a design does not do**, beside the behaviour it owns — a |
| 83 | short known-limitations note in the owning module. A stated limit stops the |
| 84 | next reader from assuming a capability that was never built. |
| 85 | - **Agents do not comment on issues or PRs** (founder, 2026-09-22). Spend the |
| 86 | time on code: evidence goes in the commit message and PR body, claims go in |
| 87 | Linear. Do not reply to review bots or post status, "superseded", or |
| 88 | "for the record" notes. The one exception is closing or superseding a human |
| 89 | contributor's PR or issue: one sentence saying why, with the link. The PR and |
| 90 | issue review workflows are disabled; re-enable one only by founder decision. |
| 91 | - **A user feature lands with its registry row.** A new command, `[features]` |
| 92 | flag, provider or user-visible feature adds or updates its row in |
| 93 | `docs/features.toml` in the same change; |
| 94 | `cargo test -p codewhale-tui --test feature_registry` fails on flag drift. |
| 95 | - **Write `close`/`fix`/`resolve #N` only when you mean it.** GitHub closes the |
| 96 | issue on merge even inside "does not close #N"; use `Refs #N` otherwise. |
| 97 | |
| 98 | ## Landing other people's work |
| 99 | |
| 100 | An external contributor's branch goes stale because *we* land things, not |
| 101 | because they did anything wrong. Treat their time as more expensive than ours. |
| 102 | |
| 103 | **The goal is the contributor's PR merging as itself.** Review it, help it |
| 104 | rebase, or fix it on their branch — that is the default path. Closing their PR |
| 105 | and re-landing the work as our own commit (`auto-close-harvested`) is the |
| 106 | fallback for a branch that truly cannot merge in reasonable time; done |
| 107 | casually it reads as taking the work even when credit is preserved. |
| 108 | |
| 109 | - **Never make a contributor rebase around our churn.** If their PR conflicts |
| 110 | only because main moved, a maintainer resolves it. |
| 111 | - Landing mechanics — merge-base diffing, mid-function conflicts, fork-push |
| 112 | refusal, the contribution gate — live in `cw-land`. Follow them instead of |
| 113 | improvising. |
| 114 | - **Preserve credit in the mechanical sense, not just the polite one.** Commit |
| 115 | authorship and `Co-authored-by` trailers must use the contributor's own |
| 116 | GitHub-linked address. `AUTHOR_MAP` and `.mailmap` are project conventions — |
| 117 | GitHub reads neither for the contribution graph. |
| 118 | |
| 119 | ## Merging under a gate |
| 120 | |
| 121 | - **A gate is its artifact.** When a rail says a PR merges only on a passing |
| 122 | acceptance record, the record must literally say PASS at merge time. "I |
| 123 | re-ran it and the failures are rows this PR does not own" is a judgement to |
| 124 | write into the artifact first, not a reason to merge past it. |
| 125 | - **Read the review thread, not the check rollup.** Green checks and an unread |
| 126 | review with confirmed findings are a merge that ships known bugs. |
| 127 | - **When the artifact is ambiguous, resolve the ambiguity — never the merge.** |
| 128 | |
| 129 | ## Claiming a test passed |
| 130 | |
| 131 | - Quote the real `test result: N passed; M failed` line, and confirm `N > 0` |
| 132 | for the tests that cover the change. `cargo test <filter>` exits 0 having run |
| 133 | zero tests when the filter matches nothing, and an exit code alone has |
| 134 | already been mistaken for a pass here. |
| 135 | - Prefer proving a regression test fails without the fix. A test that passes |
| 136 | either way pins the implementation, not the defect. |
| 137 | - Audit any hand-rolled scorer before trusting its score: quote the counts it |
| 138 | actually evaluated, not the verdict line alone. |
| 139 | - Match the evidence to the surface. Run the tests that cover the change, not the |
| 140 | whole suite, and do not repeat a check that already passed in order to commit. |
| 141 | CI owns exhaustive coverage; a full local run is for CI diagnosis or for an |
| 142 | irreducibly repository-wide change. |
| 143 | |
| 144 | ## Current contracts |
| 145 | |
| 146 | - The model-facing subagent tool is `agent`; `agent_open`/`agent_eval`/ |
| 147 | `agent_close`/`delegate_to_agent` are removed surfaces. If the shape must |
| 148 | move, move the code and add the guard test that judges the new shape. |
| 149 | - `BASE_PROMPT` in `crates/tui/src/prompts/text.rs` is the sole base prompt |
| 150 | by convention. Same rule: move the code, not the prose, if that changes. |
| 151 | - There is exactly one turn loop: `Engine::run_turn` in |
| 152 | `crates/tui/src/core/engine/turn_loop.rs`. Note that `crates/tui/src/core/` |
| 153 | is a module inside the TUI crate — it is not `crates/core`, which owns |
| 154 | request construction, bounded fragments, and thread/session types and |
| 155 | runs no turns. A guard test (`crates/core/tests/single_turn_loop.rs`) |
| 156 | fails on a second loop; changing the shape means changing the guard |
| 157 | with it. |
| 158 | - The system prompt + tool catalog are a session-pinned KV-cache prefix |
| 159 | (`docs/CACHE.md`). Any new session-context contributor must state its |
| 160 | KV-cache effect: frozen prefix vs. append-only history. Never splice a |
| 161 | volatile fact into the prefix; append it as a user-role message. |
| 162 | - These active modules are repeatedly misidentified as dead; verify consumers |
| 163 | before removal: `runtime/src/context_budget.rs`, `tui/src/model_registry.rs`, |
| 164 | `runtime/src/prompt_zones.rs`, `tui/src/tools/remember.rs`, and |
| 165 | `config/src/route/`. Native memory lives in `runtime/src/native_memory.rs`; |
| 166 | `tools/remember.rs` is its capture path. |
| 167 | - Environment-specific behavior belongs in `docs/ENVIRONMENTS.md`, not here. |
| 168 | - Blocking-call convention (#6149): code on the Tokio runtime — tool |
| 169 | handlers, engine tasks, the UI event loop, anything reached through an |
| 170 | `async` call chain — must not run blocking operations inline. Use |
| 171 | `tokio::fs` / `tokio::process`, or move the work into `spawn_blocking`; |
| 172 | a sync helper containing blocking calls runs only under `spawn_blocking` |
| 173 | or on a dedicated thread. `scripts/check-blocking-calls-budget.py` |
| 174 | ratchets the unprotected-site count. |
| 175 | |
| 176 | ## Code, migrations, and evidence |
| 177 | |
| 178 | - Product intent and observed runtime behavior outrank a test's preferred |
| 179 | implementation shape. Fix the product; do not contort production code to |
| 180 | preserve a brittle assertion. |
| 181 | - Code first, then tests. Write the implementation and prove it runs, then add |
| 182 | or adjust tests to cover what was actually built. Never write tests first and |
| 183 | never practice TDD here — this overrides any skill or default that mandates |
| 184 | it, including superpowers `test-driven-development`. Tests stay the gate |
| 185 | before a push; they are not the design driver. An existing test that only |
| 186 | encodes old behavior is evidence, not a veto: change it with the code rather |
| 187 | than bending the code to keep it green. This does not relax the rule under |
| 188 | "Claiming a test passed" — a regression test written *after* the fix still |
| 189 | has to be shown failing without it. |
| 190 | - Tests are selective evidence, not the specification. Do not add tests by |
| 191 | default. Add or retain one when it cheaply protects a high-risk behavior such |
| 192 | as safety, data integrity, protocol compatibility, or a reproduced regression. |
| 193 | - Rewrite or remove tests that duplicate coverage, freeze internals, overspecify |
| 194 | copy or layout, preserve obsolete behavior, or cost more than the risk they |
| 195 | cover. Never weaken real safety or data-integrity behavior merely to make a |
| 196 | gate pass. |
| 197 | - Prefer focused compilation, a relevant existing check, and direct product or |
| 198 | manual evidence. Run a broad suite only when the change creates a genuine |
| 199 | cross-cutting or release risk. Do not repeatedly rerun an unchanged suite. |
| 200 | - **Batch edits; compile once.** `cargo check` and test builds on this |
| 201 | workspace take minutes, so an edit→compile→edit loop spends most of its |
| 202 | time waiting on the linker. Read precisely, write every edit a coherent |
| 203 | slice needs, then compile and test once — the same errors surface either |
| 204 | way, just later and all at once. Reserve mid-slice compiles for genuinely |
| 205 | uncertain API or borrow questions where a wrong guess would cascade. |
| 206 | - Declared migrations are one-way. Once the repository adopts a replacement |
| 207 | architecture or shared spine, new work uses it and touched legacy code moves |
| 208 | toward it. Do not add another legacy call site for convenience. Keep a |
| 209 | compatibility path only for an actual external contract, and label that |
| 210 | boundary explicitly. |
| 211 | |
| 212 | Useful commands, selected according to risk rather than run ritualistically: |
| 213 | |
| 214 | ```sh |
| 215 | cargo fmt --all -- --check |
| 216 | cargo test -p codewhale-config -p codewhale-protocol |
| 217 | cargo test --workspace |
| 218 | cargo build --release -p codewhale-cli -p codewhale-tui |
| 219 | ``` |
| 220 | |
| 221 | `scripts/dev-test.sh <area|path> [filter]` maps a code area to its fastest |
| 222 | invocation — see `cw-gates` for the verification ladder and |
| 223 | `docs/BUILD_PERFORMANCE.md` for the build topology. |
| 224 | |
| 225 | Report commands actually run and distinguish source, local tests, packaged |
| 226 | artifacts, CI, and public release state. Describe the evidence actually needed |
| 227 | for the claim; a test count is not a proxy for product quality. |
| 228 | |
| 229 | Community reports, PRs, logs, and reviews are evidence. |
| 230 | |
| 231 | **Harvested contributor credit is still a rule** (the fallback path above — |
| 232 | prefer merging the contributor's PR itself). When a contributor's work |
| 233 | lands as our commit, that commit carries `Harvested from PR #N by @handle` and a |
| 234 | `Co-authored-by` naming them at their GitHub-linked address, so |
| 235 | `auto-close-harvested.yml` closes their PR with credit and the contribution |
| 236 | graph reflects reality. Canonical human identities come from |
| 237 | `.github/AUTHOR_MAP`. |
| 238 | |
| 239 | Leave unrelated work intact and keep new enforcement dry-run unless explicitly |
| 240 | approved. |
| 241 |