diff options
| -rw-r--r-- | .ai/notes.org | 2 | ||||
| -rw-r--r-- | .ai/sessions/2026-08-05-18-49-inbox-batch-quiet-form-and-anchor-scope.org | 319 | ||||
| -rw-r--r-- | .ai/workflows/sentry.org | 4 | ||||
| -rw-r--r-- | claude-rules/interaction.md | 26 | ||||
| -rw-r--r-- | claude-rules/subagents.md | 57 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/sentry.org | 4 | ||||
| -rw-r--r-- | inbox/lint-followups.org | 30 | ||||
| -rw-r--r-- | todo.org | 273 |
8 files changed, 693 insertions, 22 deletions
diff --git a/.ai/notes.org b/.ai/notes.org index b6c85f8..be6708c 100644 --- a/.ai/notes.org +++ b/.ai/notes.org @@ -85,6 +85,6 @@ Format: Markers maintained by workflows to record when they last ran. Read by other workflows that gate their behavior on freshness. :LAST_AUDIT: 2026-07-20 (open set current — this session's shipped work (working/temp, triage-source-activation, silent-until-signal, suspend detach) closed as it went; sentry cluster consolidated (merged the /schedule tasks, added cross-host-coordination); nothing shipped-but-open per git reconcile. Live finding: the Polyglot + Subprojects scouting tasks are SCHEDULED 2026-07-20 and due.) -:LAST_INBOX_PROCESS: 2026-07-31 (four work handoffs: standup-scripts proposal accepted with four changes and shipped as a212eeb; voice pattern #48 filed [#B], then its interaction.md mirror folded in on Craig's approval; a correction FYI acknowledged — my project-workflows fork claim was wrong, Phase 11 composes rather than shadows) +:LAST_INBOX_PROCESS: 2026-08-04 (ten handoffs, batch-approved: four implemented — signature paths, lint-org anchor scope, gmail pagination floor, telega 404 callback; two folded — work's link sweep as independent confirmation, KB orphans 42 → 113; four filed — [#B] teardown live-sibling gate, [#B] agent-scoped anchor default, [#C] install-lang track-mode ignores, [#C] hook message names a step not a command; all four senders replied to) Format: one =:MARKER: YYYY-MM-DD= line per workflow. Workflows overwrite their own marker on completion. diff --git a/.ai/sessions/2026-08-05-18-49-inbox-batch-quiet-form-and-anchor-scope.org b/.ai/sessions/2026-08-05-18-49-inbox-batch-quiet-form-and-anchor-scope.org new file mode 100644 index 0000000..f0a5399 --- /dev/null +++ b/.ai/sessions/2026-08-05-18-49-inbox-batch-quiet-form-and-anchor-scope.org @@ -0,0 +1,319 @@ +#+TITLE: Session Context — 2026-08-04 +#+AUTHOR: Craig Jennings +#+DATE: 2026-08-04 + +* Summary + +** Active Goal + +Startup found nine handoffs waiting and three more arrived during the session. +The whole session became one long inbox pass: evaluate twelve items across five +senders, verify every checkable claim against the source rather than the report, +and ship or file each one. + +** Decisions + +- *Verify before grading, every time.* Every report that made a factual claim + got checked against the source first. All of them held, which is the argument + for continuing rather than for stopping — the checks were cheap and the one + thing they'd have caught is the expensive one. +- *File wider than reported when the report points at something bigger.* Home + asked for =todo.org= in the installer's exclude set. The real gap is that a + repo Craig doesn't own is a third mode the installer doesn't model, since + gitignore mode commits his personal policy into someone else's tracked file. +- *Five commits, not one bundle*, because the fixes were independent and a + bundled =chore:= would have buried two behavior changes among doc edits. +- *Craig lifted the Agent-tool restriction* where the standing rules call for + dispatch, settling a conflict that had surfaced three times in two sessions. +- *Zero-tolerance on em-dashes applies to new prose even in files carrying 65 of + them*, because the profile calls the rule chosen self-discipline rather than a + reflection of his habit. That makes the existing ones the pre-rule habit the + rule corrects, not a house style to match. +- *Don't hold work that doesn't depend on the answer.* Home's item was filed + identically under every option offered, so it shipped without waiting. + +** Data Collected / Findings + +*The first dispatched review returned Request Changes with four blocking +findings, none of which I'd have caught myself.* The worst was a straight +contradiction: my new quiet-output rule told agents to let exit codes carry a +pass, while =verification.md= says read the whole output before claiming one. +Both load into every session, and the obvious targets ("tests pass", "linter is +clean") are the two commands =verification.md= names. A stale carve-out would +also have told agents to run the sentry walk inline, defeating the change +entirely. + +*The reviewer then caught me describing a fix I hadn't made* — I called finding +7 "compressed to a clause" when it was already a clause. Its judgment that the +gap mattered more than the finding is right, and it's the whole argument for +continuing the same reviewer rather than spawning a fresh one. + +*Verified against source, not taken on trust:* the signature paths really are +dead and the marks moved to =~/pictures/signature/=; =lo--spec-file-p= scopes +five checkers, not the four its docstring claimed; all five language bundles +carry the identical unconditional tooling block; teardown really has no +live-sibling gate where shutdown mode calls that gate load-bearing; every step +of =.emacs.d='s telega chain holds against telega 20260706.2147; =agent-lock= is +name-keyed and mkdir-atomic. + +*=$TMUX_PANE= is stable across an agent's Bash calls* (=%19= twice this +session). That's a fourth option archsetup's anchor proposal didn't list, and it +beats all three of theirs by keeping the resolver a pure function of its +environment. + +*The website report was overtaken by events* — the sync narrowing landed four +minutes before it was written. + +*The lint fix is confirmed working downstream.* Work replied at 18:37 that all +three synced files arrived on their evening template sync, and that their live +session anchor now lints =mechanical=0 judgment=0= against 14 judgment items +that morning, 12 of them the =level-2-dated-header= false positive. That is the +end-to-end verification I couldn't run from here, measured in the project that +reported the bug. + +They also closed the one thing I asked of them: the sentry arming prompt is +typed at =/loop= time rather than stored in a file, so there was nothing to +patch, and the correction went into their local memory instead. They kept the +verification-exemption half of that memory deliberately rather than pruning it, +on the grounds that it's the half most likely to be read past. + +*Orphan KB nodes went 42 → 113 while agent nodes doubled*, so nearly every node +written since 2026-07-01 landed unlinked. That reframes the orphan task from a +cleanup backlog into a write-path problem. + +** Files Modified + +Seven commits, all pushed. + +- =fc1a519= =lint-org.el= + tests — the anchor scope fix, suite 77 → 82. +- =7637a97= =triage-intake.telegram.org= — =#'ignore= on both =loadChats= calls. +- =57dae70= =triage-intake.personal-gmail.org= — the date-slice floor. +- =771c669= =protocols.org= — signature section rewritten to a three-row table. +- =91ac5a1= =todo.org= — four filings from the batch. +- =cf9910d= =todo.org= — the collaborator-repo installer gap. +- =588fbf6= =interaction.md=, =subagents.md=, =sentry.org= — quiet output plus + the Output-Destination Override. + +KB: promoted 2 / consulted yes + +** Next Steps + +1. *Craig's two standing reminders are both still open* and both need his + decisions rather than execution: the context-engineering rightsizing (four + decisions, docs one commit behind) and the sentry spec deep read, now three + weeks old. +2. *The teardown live-sibling gate =[#B]=* is the sharpest thing filed tonight — + it would have killed a running session. Needs a real two-session drill, so + it's not =:solo:=. +3. *The agent-anchor default =[#B]=* now carries the =$TMUX_PANE= option; decide + between it and archsetup's three before building. +4. *The inbox Stop hook fired three times* on an item that was mid-disposition, + where protocols.org says it should step aside on re-entry. Worth checking + whether the soft-nudge is behaving as designed. +5. Twenty-nine tasks remain unreviewed past a week. + +* Session Log + +** 00:30 — Startup + +Ran the startup workflow. Phase A.0: rulesets already current, =make install= +had nothing new to link, project repo clean and up to date. Phase A: templates +synced cleanly, no session-context anchor from a prior session (last wrap was +clean), language bundle silent, spec-sort and host-identity probes silent. + +Findings to carry: 29 top-level tasks unreviewed for >7 days, 3 items in the +roam global inbox, and nine new handoffs in =inbox/= since the 2026-07-31 +process marker — one from website, one KB hygiene report, four from work, two +from archsetup. + +** 00:45 — Inbox pass: evaluation + +Ran the value gate and skeptical review over all nine handoffs, verifying every +checkable claim against the source rather than taking the reports at face value. +All four verifications confirmed the senders: + +- The signature paths in =protocols.org= really are dead; dotfiles moved the + marks to =~/pictures/signature/= and added an initials stamp the section + never mentions. +- =lo--spec-file-p= scopes out the todo-format family for =/docs/specs/= only, + and a session anchor has the same property by instruction (=protocols.org= + line 120 prescribes the dated =** = log heading). Its docstring says four + checkers; the call site excludes five. +- All five language bundles append the same unconditional tooling block + (=.claude/=, =CLAUDE.md=, =githooks/=) in =gitignore-add.txt=, so the + track-mode defect work hit is not python-specific. +- Teardown mode really has no live-sibling gate: shutdown mode gates on + =cj/ai-term-live-count= and calls it "the load-bearing safety of the whole + feature," while teardown drops its sentinel unguarded and the hook then kills + the whole =aiv-<project>= tmux session, windows and all. + +One thing I found that archsetup's anchor proposal missed: =$TMUX_PANE= is +exported into the agent's Bash environment and stable across calls (=%19= on +two separate invocations this session). That is a fourth option they did not +list, and it beats all three of theirs — the resolver stays a pure function of +the environment, with no minting and no state file. + +The website report is partly overtaken by events: the per-path sync narrowing +(=f69dc22=) landed four minutes before that handoff was written, so the specific +root cause it names — one dirty =sentry.org= blocking all three rsyncs — no +longer holds. The asymmetry it describes does. + +A tenth handoff arrived from =.emacs.d= at 00:31, mid-evaluation, proposing a +one-line fix for a repeating telega 404. Craig approved the batch as +recommended. + +** Inbox pass: applied + +(Note on times: =date= read 00:30 at startup and 09:51 an hour of work later, so +the machine's clock moved under this session. Timestamps below are what =date= +returned when the entry was written; the earlier headings keep what it returned +then. Not chased — flagging it so the sequence doesn't read as garbled.) + +Four implemented, two folded, four filed. + +*Implemented.* =protocols.org='s Signature Image section now points at +=~/pictures/signature/= as a three-row table and names the README that carries +the sizing guidance — the old prose named two paths that no longer exist. I +described the stow source as "Craig's dotfiles repo" rather than the specific +path work quoted: the doc had already rotted once by pinning +=~/code/archsetup/dotfiles/=, and the marks now resolve through =~/.dotfiles/=. +The personal-gmail plugin's date-slice recipe is now labelled a floor rather +than a complete read, with home's 250-vs-291 measurement and the rule for which +uses can tolerate it. The telegram plugin passes =#'ignore= to both =loadChats= +calls, with the quick reference updated in the same pass so the two can't drift. + +=lint-org.el='s =lo--spec-file-p= became =lo--todo-format-exempt-p=, widened to +cover the live anchor, per-agent anchors, and the =.ai/sessions/= archives. +Work flagged honestly that they'd measured only =level-2-dated-header= and were +inferring the rest; the composite test I wrote red answered it — the other four +do fire on an anchor. The archives were mine to add: same file, same shape, +later in its life, and sentry lints them. Suite went 77 → 82. + +*Folded.* Work's link sweep into the existing =../../= links bug as independent +confirmation from the consuming side — same seven sites, and they correctly +excluded the =inbox.org= template placeholder, which the fix must not sweep up. +The KB hygiene report into the orphan-review task, where the number reframes it: +orphans went 42 → 113 while agent nodes doubled, so nearly every node written +since 2026-07-01 landed unlinked. That makes the backlog a symptom of the write +path rather than a cleanup queue. + +*Filed.* =[#B]= teardown's missing live-sibling gate (the most serious — it +would have killed a running session), =[#B]= the agent-scoped anchor default, +=[#C]= install-lang's unconditional tooling ignores, =[#C]= the hook message +that names a step instead of a command. + +** Shipped as five commits, pushed + +=fc1a519= the lint-org scope fix, =7637a97= the telega callback, =57dae70= the +gmail pagination floor, =771c669= the signature paths, =91ac5a1= the four task +filings. Green suite before and after, zero new lint issues, mirror in sync, +=0 0= against upstream after the push. + +Split into five rather than one because they're independent fixes, and a +bundled =chore:= would have buried the two behavior changes among the doc edits. + +*The review gate ran inline, not isolated.* The =publish= skill requires a +dispatched reviewer on every commit and =subagents.md= names it as the standing +isolation-override case, but this session's instructions bar the Agent tool +unless Craig asks. The skill's own fallback covers it, and I said so plainly at +the gate rather than letting an inline review pass as an isolated one. It did +earn its keep: it caught my telega comment claiming the noise multiplied "across +every project holding a synced copy," when there is one daemon and the synced +copies only multiply which project can trigger it. + +That conflict has now fired twice in two sessions, and work's new handoff makes +it three — its second rule is "dispatch repeated passes to a subagent." One +decision from Craig settles all three. + +** Two more handoffs mid-commit + +=home= reports the installer never excludes =todo.org=, so it lands tracked in a +collaborator's repo. Verified: both =install-ai.sh='s gitignore block and +=sweep-gitignore-tooling.sh='s =IGNORE_SET= carry =.ai/=, =.claude/=, +=CLAUDE.md=, =AGENTS.md= and nothing else. The deeper finding is that the +collaborator case is a *third* mode the installer has no notion of — in a repo +Craig doesn't own, the whole set belongs in =.git/info/exclude= rather than +=.gitignore=, since appending to =.gitignore= pushes his personal policy into +someone else's tracked file. Ratowsky already worked around it exactly that way. + +=work= relays a direct Craig directive: quiet form always, and dispatch repeated +scheduled passes. Both rules are right and I've been violating the first all +session. + +Home's item got filed as =[#C]= and committed (=cf9910d=) without waiting for +Craig, because it was filed identically under every option I'd offered him — so +it wasn't a fork, and holding it would have been ceremony. + +** Craig lifted the Agent-tool restriction, and the first dispatched review paid for itself + +He chose option 1: implement quiet form into =interaction.md= and +=subagents.md=, with the Agent tool allowed where the standing rules call for +dispatch. So the pre-commit review for the rules change went to an isolated +reviewer, the first this session. + +It returned *Request Changes* with four blocking findings, and every one is +something I would not have caught reviewing my own diff: + +1. *The quiet rule contradicted =verification.md= outright.* I wrote "a check + that returns the expected result prints nothing" and prescribed + =cmd >/dev/null 2>&1=, while =verification.md= says read the whole output + before claiming a pass. Both files load into every session, so an agent + would have had to resolve that itself, every time — and the obvious targets + ("tests pass", "linter is clean") are exactly the two commands + =verification.md= names. My rule was instructing agents toward the false + pass that file exists to prevent. +2. *My idiom threw away the diagnostic it then demanded.* =2>&1= to + =/dev/null= means the failure branch prints only the placeholder string I + typed, eight lines above my own sentence saying deviations print in full. +3. *"Run these two checks" now sat over three subsections*, and the new one + inverted the section's direction: the other two prevent a spawn, mine + justifies one. +4. *Two of three parallel override enumerations went stale*, and one collided + with the motivating case — "Don't Subagent At All" refuses dispatch for a + known target under ~10 tool calls, which is precisely the shape of the + sentry walk. An agent landing there would have correctly concluded "run it + inline," defeating the whole change. + +Fixed all seven (the three Minor ones too: background is the load-bearing word, +the thread boundary for lock and commits was left to inference, the anecdote was +told three times). Verified the lock claim rather than asserting it — +=agent-lock= is name-keyed and mkdir-atomic, and its header says it exists +because every agent Bash call is its own shell, so a refresh from the subagent +holds the lock the main thread acquired. + +Sent back to the *same* reviewer per the publish flow, so it can check its own +findings against the new text rather than a fresh reviewer re-litigating. + +Round two: *Approve*. It rated findings 1 and 4 closed more thoroughly than it +asked, verified the =agent-lock= claim against the helper's source rather than +taking my word, and raised one new Minor I'd introduced — my preamble said both +overrides lift both pre-dispatch checks, but Availability is a hard capability +constraint, not an efficiency rule, and no override can conjure a spawn +mechanism that isn't there. Fixed rather than deferred. + +*It also caught me describing a fix that didn't happen.* I reported finding 7 +(the anecdote told three times) as "compressed to a clause"; the diff shows it +was already a clause and I changed three words. Its judgment — that the gap is +worth more than the finding was — is right, and it's the exact failure mode the +re-review loop exists to catch: had it trusted my summary instead of re-reading +the text, I'd have gotten away with it. Worth remembering that the loop's value +is the reviewer checking claims against the diff, not against my account of the +diff. + +** Craig ran /voice bare, mid-flow + +Read it as the in-progress draft: the new rule prose, prose mode (these are +documents he reads in an editor, so personal mode's artifact patterns would +misfire). It found 12 em-dashes in what I'd written, plus a semicolon, +uncontracted forms, and stress italics. + +The judgment call worth recording: =interaction.md= and =subagents.md= already +carry 65 em-dashes between them, so new prose following zero-tolerance is +locally inconsistent with its surroundings. I applied the rule anyway, because +the profile says zero-tolerance is chosen self-discipline rather than a +reflection of his habit — which makes the existing 65 the pre-rule habit the +rule exists to correct, not a house style to match. Flagged it to him rather +than deciding silently. + +Work's handoff is replied to and retired, and the inbox is empty. Shipped as +=588fbf6=, pushed, tree clean and even with upstream. diff --git a/.ai/workflows/sentry.org b/.ai/workflows/sentry.org index b25fc14..0e8f819 100644 --- a/.ai/workflows/sentry.org +++ b/.ai/workflows/sentry.org @@ -91,6 +91,10 @@ Every reclaim of a stale lock surfaces in the digest — the helper prints the r Each cycle, after acquiring the single-runner lock and verifying branch state (below), walks the pass list in order. Every pass follows the same four-step contract: +*Run the walk in a BACKGROUND subagent, not the main thread.* The pass list is roughly ten tool calls and it repeats every cycle, while the main thread's tool output lands in the Emacs buffer Craig is working in. Eleven cycles inline on 2026-08-05 filled his workspace with output that almost entirely confirmed nothing had changed. Backgrounding is the load-bearing part: a background agent's output goes to a file, while a foreground dispatch puts it straight back in his terminal. The main thread then reports only what the quiet-output rule allows: deviations, plus the one-line heartbeat at cycle-end. See the Output-Destination Override in =subagents.md= and the quiet-output rule in =interaction.md=. A recurring prompt that tells the agent to walk the list inline is overriding this. Fix the prompt rather than the workflow. + +*Where the thread boundary falls.* The dispatched agent owns the whole walk, which means the per-pass work, the =session-context.org= entries, the per-pass commits, and the between-pass lock refreshes all happen inside it. The main thread keeps what brackets the walk: the entry gates, acquiring and releasing the single-runner lock, branch-state verification, and the cycle-end digest. The lock survives the boundary because =agent-lock= is keyed by name rather than by process, so a refresh from the subagent holds the same lock the main thread acquired. + 1. *Probe* — a cheap existence check for the pass's target (named per pass below). Absent → the pass is one skip line in the digest and nothing more. This is what makes the pass list portable: passes self-activate where their target exists and stay silent elsewhere, with zero per-project configuration. 2. *Work* — run the pass under the unattended contract. Quick, solo, already-agreed mechanical actions execute. Anything destructive or requiring judgment does *not* execute — it appends to the morning-approval queue (what, why, the exact command or edit that fires on approval). A pass runs fully or not at all; there is no reduced-form pass. diff --git a/claude-rules/interaction.md b/claude-rules/interaction.md index b5798bd..746f5c9 100644 --- a/claude-rules/interaction.md +++ b/claude-rules/interaction.md @@ -97,6 +97,32 @@ In conversational output to the user, do not use Markdown bold (`**...**`) or in This governs **chat output**, not the Markdown source of rule files, specs, or docs the user reads in an editor — those keep normal Markdown formatting. The constraint is the terminal rendering of the live conversation. +## Quiet Output: A Check That Passes Says Nothing + +A check that returns the expected result prints nothing. Only deviations print, plus at most one summary line. + +**Why:** tool output isn't free. Craig runs Claude Code inside Emacs EAT, so every Bash stdout lands in the buffer he's working in, where the command's output and his work compete for one screen. The reverse-video rule above governs the styling of what reaches his terminal. This governs the volume, which is the larger cost. + +**The failure it closes** is reading "keep output minimal" as a rule about prose, and treating tool output as exempt because it isn't prose. Work ran eleven hourly sentry cycles on 2026-08-05, each about ten Bash calls printing in full. Nearly every line confirmed nothing had changed: roam current, staleness unchanged, lint at its known counts, tree clean. Craig's instruction that day was "I want you to switch to the quiet form ALWAYS." + +**How to apply:** + +- Let the exit code carry the pass, but capture the output rather than discarding it, so the failure branch can show what went wrong: + + out=$(cmd 2>&1) || { echo "DEVIATION: <what>"; echo "$out"; } + + Don't write `cmd >/dev/null 2>&1 || echo "DEVIATION: ..."`. That throws the diagnostic away down the one branch that needs it, leaving only the placeholder you typed. +- Grep for problems rather than for confirmation. Piping a report through `grep DEVIATION` beats printing the report. +- Compare against the known-good value and print only a mismatch (a count, a SHA, a status string). +- One closing summary line is fine ("checks done", "3 of 11 passes had findings"). A per-item roll call confirming each success is not. +- When you genuinely need a command's output, filter it to the anomalies first. + +Quiet isn't silent. A deviation, a finding, and anything Craig asked to see all print in full. Suppressing those is the opposite failure and a worse one. The rule removes confirmations, never signal. + +**Verification gates are exempt, and the exemption is load-bearing.** [`verification.md`](verification.md) requires reading a check's whole output before claiming it passed, because an exit code can lie. A suite that silently skipped every test exits 0. A linter can exit 0 holding warnings. A gate running against a hand-maintained file list exits 0 without ever seeing your new file. So when the command is the evidence for a completion claim (the pre-commit test run, the linter, the type checker, a bug's reproduction steps), read the real output and say what it said. Quiet form governs routine checks that only confirm the expected state. It never buys a cheaper way to claim something passed. Where the two rules meet, `verification.md` wins. + +This is about the main thread's output, because that's what reaches his terminal. A background subagent writes to a file instead, which is why a repeated multi-step pass gets dispatched rather than run inline. See the Output-Destination Override in [`subagents.md`](subagents.md). + ## Showing Craig Visuals Craig runs Claude Code inside Emacs EAT (through tmux). EAT renders SendUserFile and inline terminal images as an `[image] path.png` text line — the visual itself never appears. In one session ~20 renders went out that way and Craig approved UI he had never seen (takuzu, 2026-07-11). Never rely on SendUserFile or inline image display to show a visual. SendUserFile stays fine for *delivering* a file; it just doesn't display one. diff --git a/claude-rules/subagents.md b/claude-rules/subagents.md index e52d906..b36abba 100644 --- a/claude-rules/subagents.md +++ b/claude-rules/subagents.md @@ -10,7 +10,10 @@ deliberately, not reflexively. ## Pre-Dispatch Checks Run these two checks before any spawn. Both can send the work back to the -main thread without the spawn ever happening. +main thread without the spawn ever happening. The two overrides below +(Isolation, Output Destination) point the other way, at dispatch, and each +lifts the Cost gate. Neither touches Availability: no override can conjure a +spawn mechanism that isn't there. ### Availability @@ -36,7 +39,39 @@ at dispatch time. Every size-based rule in this file — the cost gate here, "Don't Subagent At All", the trivial-work anti-pattern — is subject to the isolation override -below. +and the output-destination gate below. + +## Output-Destination Override: When the Terminal Is the Cost + +The cost gate above weighs the handoff against the work. The isolation +override below fires when the main thread is disqualified from judging. This +third justification is neither. The work is trivial, the main thread is +perfectly capable of it, and dispatching is still correct, because a background +subagent's output goes to a file while the main thread's goes to Craig's +terminal. Background is the operative word. A foreground dispatch puts the +output right back where it started. + +It fires for a **repeated multi-step pass on a schedule**: a recurring battery +of checks, a monitor loop's per-cycle body, any prompt that walks the same list +every N minutes. Ten Bash calls once is fine inline. Ten Bash calls every hour +for eleven hours is a hundred and ten blocks of output in the buffer he's +working in, which is what happened on 2026-08-05. + +Dispatch those, and let the quiet-output rule in `interaction.md` govern what +the main thread then says about the result: deviations, plus one line per cycle. +The justification is where the output lands, not how hard the work is. So the +size gates in this file don't apply, the same way they don't apply under the +isolation override. In particular, "the target is already known and the work +fits in under ~10 tool calls" is exactly the shape of a recurring check battery, +so that gate would otherwise refuse every case this one exists to catch. + +The inverse keeps this from becoming a licence to dispatch everything. Work that +produces little output, or output Craig actually wants to watch, gains nothing +from the handoff and still pays the contract cost. A one-off check stays inline +and prints quietly. + +Craig's directive, 2026-08-05: quiet form always, and hand a repeated pass to a +subagent. ## Isolation Override — When Size Doesn't Gate @@ -123,8 +158,13 @@ to the user to adjudicate, not back to the author's own judgment. ### Don't Subagent At All -Unless the Isolation Override applies — these are efficiency rules, and they -lapse when the main thread's own context is what makes its answer untrustworthy. +Unless one of the two overrides applies. These are efficiency rules, and they +lapse under the Isolation Override (the main thread's own context is what makes +its answer untrustworthy) and under the Output-Destination Override (the pass +repeats on a schedule, so its output floods Craig's terminal). The first bullet +below is the one the second override most often lifts. A recurring check battery +is always a known target in under ~10 calls, and that's not a reason to run it +inline. - **The target is already known** and the work fits in under ~10 tool calls. - **Single-function logic** — one Read + one Edit is faster than briefing @@ -202,8 +242,10 @@ fix), then dispatch the fix with a specific contract. - **Retrying a failed subagent task in the orchestrator** — pollutes context. Dispatch a fix agent instead. - **Subagenting trivial work** — one Read + one Edit doesn't need an - agent; spawn overhead exceeds benefit. Except under the Isolation - Override, where a one-line diff still gets its own reviewer. + agent; spawn overhead exceeds benefit. Except under either override: the + Isolation Override, where a one-line diff still gets its own reviewer, and + the Output-Destination Override, where a trivial pass that repeats hourly + gets dispatched for where its output lands. - **Reviewing your own change inline** — the mirror-image failure, and the more expensive one. Skipping a dispatch to save overhead on a small diff costs a review that could only have come from outside your context. @@ -219,3 +261,6 @@ fix), then dispatch the fix with a specific contract. see `verification.md`. - Testing discipline applies to subagent-produced tests too — see `testing.md`. +- What the main thread prints once a pass is dispatched: see the + quiet-output rule in `interaction.md`. It's the other half of the + Output-Destination Override above. diff --git a/claude-templates/.ai/workflows/sentry.org b/claude-templates/.ai/workflows/sentry.org index b25fc14..0e8f819 100644 --- a/claude-templates/.ai/workflows/sentry.org +++ b/claude-templates/.ai/workflows/sentry.org @@ -91,6 +91,10 @@ Every reclaim of a stale lock surfaces in the digest — the helper prints the r Each cycle, after acquiring the single-runner lock and verifying branch state (below), walks the pass list in order. Every pass follows the same four-step contract: +*Run the walk in a BACKGROUND subagent, not the main thread.* The pass list is roughly ten tool calls and it repeats every cycle, while the main thread's tool output lands in the Emacs buffer Craig is working in. Eleven cycles inline on 2026-08-05 filled his workspace with output that almost entirely confirmed nothing had changed. Backgrounding is the load-bearing part: a background agent's output goes to a file, while a foreground dispatch puts it straight back in his terminal. The main thread then reports only what the quiet-output rule allows: deviations, plus the one-line heartbeat at cycle-end. See the Output-Destination Override in =subagents.md= and the quiet-output rule in =interaction.md=. A recurring prompt that tells the agent to walk the list inline is overriding this. Fix the prompt rather than the workflow. + +*Where the thread boundary falls.* The dispatched agent owns the whole walk, which means the per-pass work, the =session-context.org= entries, the per-pass commits, and the between-pass lock refreshes all happen inside it. The main thread keeps what brackets the walk: the entry gates, acquiring and releasing the single-runner lock, branch-state verification, and the cycle-end digest. The lock survives the boundary because =agent-lock= is keyed by name rather than by process, so a refresh from the subagent holds the same lock the main thread acquired. + 1. *Probe* — a cheap existence check for the pass's target (named per pass below). Absent → the pass is one skip line in the digest and nothing more. This is what makes the pass list portable: passes self-activate where their target exists and stay silent elsewhere, with zero per-project configuration. 2. *Work* — run the pass under the unattended contract. Quick, solo, already-agreed mechanical actions execute. Anything destructive or requiring judgment does *not* execute — it appends to the morning-approval queue (what, why, the exact command or edit that fires on approval). A pass runs fully or not at all; there is no reduced-form pass. diff --git a/inbox/lint-followups.org b/inbox/lint-followups.org index 21a5ed3..b140944 100644 --- a/inbox/lint-followups.org +++ b/inbox/lint-followups.org @@ -1,18 +1,18 @@ * 2026-07-20 Mon — Task-review health: 1 top-level [#A]/[#B]/[#C] tasks unreviewed for >30 days (daily review may have slipped) -* lint-org follow-ups — todo.org (2026-07-31) -** TODO misplaced-heading — Possibly misplaced heading line (line 2320) -** TODO link-to-local-file — Link to non-existent local file "working/hook-fail-open/validate-el.diff" (line 2296) -** TODO misplaced-planning-info — Misplaced planning info line (line 2285) -** TODO link-to-local-file — Link to non-existent local file "working/hook-fail-open/pre-commit.diff" (line 2280) -** TODO misplaced-planning-info — Misplaced planning info line (line 2265) -** TODO org-table-standard — table violates the org-table standard: no closing rule; missing rule between rows — wrap-org-table.el reflows it (line 371) -** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 529) -** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 532) -** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 539) -** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 547) -** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 550) -** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 559) -** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 657) +* lint-org follow-ups — todo.org (2026-08-05) +** TODO misplaced-heading — Possibly misplaced heading line (line 2593) +** TODO link-to-local-file — Link to non-existent local file "working/hook-fail-open/validate-el.diff" (line 2569) +** TODO misplaced-planning-info — Misplaced planning info line (line 2558) +** TODO link-to-local-file — Link to non-existent local file "working/hook-fail-open/pre-commit.diff" (line 2553) +** TODO misplaced-planning-info — Misplaced planning info line (line 2538) +** TODO org-table-standard — table violates the org-table standard: no closing rule; missing rule between rows — wrap-org-table.el reflows it (line 607) +** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 786) +** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 789) +** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 796) +** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 804) +** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 807) ** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 816) -** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 825) +** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 930) +** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1089) +** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1098) @@ -179,6 +179,242 @@ The gate already exists and is nearly right. =scripts/lint.sh='s =check_md_links Found by the adversarial reviewer on the =references/= fix, 2026-07-28, as the sibling class the original report missed. +*** 2026-08-04 Tue @ 09:51:13 -0500 Independently confirmed by work's link sweep +Work swept all 533 tracked org files in their project (sentry cycle, 2026-08-03) +and landed on the same defect from the opposite direction — a consuming project +finding the dead targets rather than rulesets predicting them. Their table +matches this task site for site: task-review ×3 plus one to =docs/design/=, +task-audit ×1, open-tasks ×1, suspend ×1. + +Two things the confirmation adds. They checked =inbox.org=:97's +=[[file:working/<slug>/proposed.diff]]= and correctly excluded it — that one is +a template placeholder inside an example block, so the fix must not sweep it up. +And they independently proposed the same fix direction I'd filed, plus an +absolute =~/code/rulesets/...= target as an alternative to prose. I'd still +prefer prose: an absolute path resolves in both trees but bakes a machine layout +into a file that ships everywhere. + +Nothing to change in the plan; this raises confidence that the seven sites are +the whole set. + +** TODO [#B] Teardown has no live-sibling gate :bug: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-04 +:END: +Wrap-up's teardown mode kills the =aiv-<project>= tmux session without checking +whether another agent is living in it. Archsetup hit this on 2026-08-04: two +primaries in one project, two windows in one tmux session, and teardown would +have taken both. They caught it by reading the pane layout before dropping the +sentinel and aborted the wrap by hand. + +Shutdown mode already has exactly this guard — it gates on +=cj/ai-term-live-count= and =wrap-it-up.org= calls that gate "the load-bearing +safety of the whole feature." Teardown, twenty lines away, has nothing. Verified +in the canonical: Step 6's teardown branch drops =/tmp/ai-wrap-teardown-<project>= +unconditionally. + +Grading: Critical severity (kills another agent's live session and its +in-progress work — the anchor survives, but anything unwritten does not) × +rare edge case (needs two primaries in one project) = P2 = =[#B]=. + +Fix direction, and archsetup's own suggestion is the better one: gate on the +pane count of *this* project's session (=tmux list-panes -t aiv-<project>=) +rather than reusing the machine-wide =cj/ai-term-live-count=. Live-count is +global, so reusing it would abort a legitimate teardown whenever any unrelated +project had a session up. The narrow check also keeps the fix inside rulesets — +no =.emacs.d= handoff needed, unlike anything routed through =ai-term.el=. + +Add the check in both places, because the window between them is real: at +sentinel-drop (where the decision is made and can be reported in the +valediction) and again in the hook before it consumes the sentinel (a sibling +can arrive in between). The hook already re-verifies the clean-tree certificate, +so it has the shape for a second gate. + +Not =:solo:= — the verification that matters is a live two-session drill in +Craig's terminal, and a fix that passes bats while still killing a real sibling +is exactly the failure mode here. + +Source: inbox/2026-08-04-0023-from-archsetup-addendum-to-today-s-agent-anchor.org + +** TODO [#B] Agent-scoped session anchor as the default :spec: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-04 +:END: +Archsetup proposes that =.ai/session-context.d/<id>.org= become the path +always, rather than the =AI_AGENT_ID=-set special case. Their argument is that +the unset case is also the common case: every agent wants an anchor that is +its own, and the single-agent situation is just the one with no contention yet. +Defaulting to the directory removes the race by construction. + +The collision that prompted it was real. Two archsetup sessions both resolved +to =.ai/session-context.org= and interleaved their logs; nothing was lost, but +only because the second one noticed and appended rather than writing. At wrap +they had to hand-split the file to avoid archiving the sibling's in-progress +record along with their own. + +The obstacle is protocols.org's invariant: =session-context-path= is called +many times per session and must return the same path every time, so it must +never mint an id itself. Archsetup lists three ways out — derive from something +already stable, let the resolver mint once and persist, or make every launcher +set =AI_AGENT_ID= — and leans on the third. I don't think the third can work: +a bare =claude= is always available, protocols.org already concedes it runs +full primary startup regardless, and their own collision was two bare +invocations. + +There's a fourth option they didn't list, and I think it beats all three: +=$TMUX_PANE= is exported into the agent's Bash environment and is stable across +calls (measured =%19= on two separate invocations, 2026-08-04). A resolver that +falls back to the pane id when =AI_AGENT_ID= is unset stays a *pure function of +the environment* — no minting, no state file, no clearing rule — which is +exactly the invariant the three listed options each have to bargain with. + +What to check before committing to it: pane ids are unique per tmux server but +reset when the server restarts, so the collision moves rather than vanishing +(narrower, since an old anchor is archived at wrap). And an agent outside tmux +has no =$TMUX_PANE= at all, so the fallback needs its own fallback — possibly +the controlling terminal, possibly the current unscoped path. + +Also decide in the same pass: the absence of =.ai/session-context.org= is +currently the signal that the last session wrapped cleanly, and both startup and +wrap-up key on it. Moving to a directory turns that into "is the directory +empty," and both call sites have to change together or a crashed session stops +being detectable. + +Their second proposal — a startup roster check so a bare invocation learns a +sibling is live — is already scoped in the helper-agent task ("Startup +detection-first: the roster check runs before Phase A.0's pulls"), so it belongs +there rather than here. Worth sequencing: the roster check surfaces the +collision, this removes it. + +Source: inbox/2026-08-03-1443-from-archsetup-proposal-from-a-concrete-collision-in.org + +** TODO [#C] Installing into a repo Craig doesn't own has no mode :bug: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-05 +:END: +=todo.org= is in neither installer's exclude set, so it lands untracked-but-unignored +in a collaborator's repo and shows up in their =git status=. Home hit this +installing into ratowsky. Verified: =install-ai.sh='s gitignore block and +=sweep-gitignore-tooling.sh='s =IGNORE_SET= both carry =.ai/=, =.claude/=, +=CLAUDE.md= and =AGENTS.md=, and nothing else. The workflows expect =todo.org= at +the project root, so any project running them grows one. + +Adding =todo.org= to both sets fixes the symptom. The gap underneath is bigger, +and it's the part worth designing: *a repo Craig doesn't own is a third mode the +installer doesn't model.* Both current modes assume the repo is his. Track mode +commits the tooling; gitignore mode appends the set to =.gitignore=, which in +someone else's repo commits his personal policy into their tracked file. That is +the same defect one layer up from the one reported, and it ships today for every +collaborator install that picks gitignore mode. + +The right mechanism there is =.git/info/exclude=, which is local, untracked, and +invisible to the repo's owner. Ratowsky already worked around it exactly that +way, for both =todo.org= and the rest of the personal layer, which is good +evidence the third mode is what the situation actually wants rather than a +generalization from one case. + +So the fix has two halves: add =todo.org= to the exclude set, and give +=install-ai.sh= a collaborator mode that writes the whole set to +=.git/info/exclude= instead of =.gitignore=. Worth checking on the way whether +=inbox/=, =working/= and =temp/= want the same treatment there — the installer +creates =inbox/= unconditionally in both modes. + +Grading: Minor severity (a personal task file surfaces in a colleague's +=git status=, and an accidental =git add= commits it; nothing breaks and nothing +is lost) × some users sometimes (collaborator-repo installs only) = P3 = =[#C]=. + +Not =:solo:= — the third mode needs a call on how the installer detects or is +told that a repo isn't Craig's, and that's a design decision rather than a +mechanical fix. + +Source: inbox/2026-08-05-1418-from-home-installer-gap-found-installing-ai-into.org + +** TODO [#C] install-lang ignores tooling a track-mode project tracks :bug:solo: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-04 +:END: +Every language bundle's =gitignore-add.txt= opens with the same unconditional +block — =.claude/=, =CLAUDE.md=, =githooks/= — which =install-lang.sh= appends +to the target's =.gitignore=. In a track-mode project those three lines assert +the opposite of the project's actual policy. Work hit it installing the python +bundle into a project that tracks both =CLAUDE.md= and =githooks/pre-commit=. + +Verified across the whole set: bash, elisp, go, python and typescript all carry +the identical header block, so this is not python-specific. The build-artifact +entries below it (=*.pyc=, =htmlcov/=, =node_modules/=, =*.elc= …) are correct +unconditionally — only the tooling header is mode-dependent. + +Nothing breaks today, which is why it went unnoticed: git keeps honoring a path +that is already tracked. The failure is latent and quiet — a *new* file added +under =githooks/= goes untracked with no warning, so a hook that should travel +to the other daily driver silently doesn't. And the =.gitignore= now +misrepresents the project's own policy to whoever reads it next. + +Grading: Major severity (a new hook or rules file silently untracked, and +nothing signals it — the change just fails to reach the other machine) × some +users sometimes (a track-mode project installing a language bundle) = P3 = +=[#C]=. + +Fix, specified so it needs no design call: split =gitignore-add.txt= into the +tooling header and the build-artifact body, and append the header only in +gitignore mode. The mode detection already exists — =scripts/sweep-gitignore-tooling.sh= +recognizes both the anchored (=/.ai/=) and unanchored (=.ai/=) ignore styles for +exactly this purpose; reuse its predicate rather than writing a second one that +can disagree with it. Track mode gets the build-artifact body alone, plus a +comment saying why the tooling entries were omitted, so a later install isn't +read as having forgotten them. + +Done when: all five bundles split, the predicate shared with the sweep script +rather than duplicated, bats covering both modes against both ignore styles, and +=make test= green. + +Smaller thing from the same report, worth doing in the same pass: =install-lang= +seeds =coverage-makefile.txt= at the project root unconditionally, so a project +with no suite for those targets gets a stray fragment. Either gate the seed or +say in the file what it's for. + +Source: inbox/2026-08-02-2210-from-work-install-lang-mis-ignores-a-track-mode.org + +** TODO [#C] Stop hook blocks on a step its workflow may not carry :bug:quick:solo: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-04 +:END: +The wrap teardown hook blocked a wrap in website because that project's synced +=wrap-it-up.org= predated the =git-worktree-gate certify= step the hook enforces. +The block message told them to rerun the final wrap verification, which doesn't +point at the fix, so diagnosing it meant reading the hook's source. + +The underlying asymmetry is real: the hook reaches a project as a =~/.claude= +symlink from =make install=, which startup runs unconditionally in Phase A.0, +while the workflow that drives it arrives by the *guarded* rsync in Phase A. The +two halves of one feature can move independently, and nothing signals the skew +until the hook fires. + +Partly overtaken by events, and the report says so honestly without knowing it: +the per-path sync narrowing (=f69dc22=) landed four minutes before that handoff +was written. The specific cause named — one dirty =sentry.org= withholding all +three rsyncs — can't happen now. What survives is the general case: a workflow +withheld for its *own* dirt, or a project branch behind upstream (that guard is +still all-or-nothing), still runs old against a new hook. + +Grading: Minor severity (a blocked wrap with a misdirecting message; the +sentinel survives, running the gate by hand clears it, nothing is lost) × some +users sometimes = P3 = =[#C]=. + +Fix, and take only the first half: make the hook's block message name the +command (=git-worktree-gate certify=) rather than describing a workflow step. +That is correct whatever version of the workflow the project holds, which is +precisely the property the skew breaks — a message that names the *step* is +another thing that can drift, while a message that names the *command* can't. + +The report's second suggestion — startup warns when a rulesets-owned hook is +newer than the project's synced copy of the workflow that drives it — is a +version-skew detector, and a real idea, but it needs a hook-to-workflow mapping +that nothing maintains today. Leaving it here as a note rather than filing it: +if the message fix doesn't stop the class, that's the escalation. + +Source: inbox/2026-07-31-2247-from-website-drift-report-from-website-s-wrap-up.org + ** TODO [#C] start-work Phase 7 still summarizes the old publish flow :chore:solo: :PROPERTIES: :LAST_REVIEWED: 2026-07-28 @@ -484,6 +720,27 @@ Both found 2026-07-23 reading =claude-templates/bin/agent-text= and =scripts/sig Grading: Minor severity for both (one is a latent hang in a tool with a documented fallback, the other a warning on an account nothing currently depends on) x most users, frequently (the stale warning is a standing condition on this host; the hang needs a stall) = P3 = [#C]. ** TODO Manual testing and validation +*** Telegram scan — no spurious 404 after the loadChats callback fix +What we're verifying: passing =#'ignore= to =telega--loadChats= stops the two +=Telega error 404: Not Found= lines each Telegram scan printed into the echo area +and =*Messages*=, without changing what the scan reads. The fix is code-verified +against telega 20260706.2147 but has never been run — it needs a live telega, so +it can't be checked from an agent session. +- Clear the decks so the count is unambiguous. +#+begin_src emacs-lisp +(with-current-buffer "*Messages*" (let ((inhibit-read-only t)) (erase-buffer))) +#+end_src +- Run a Telegram triage scan (the =triage-intake= telegram source). +- Check what the chat table holds and whether the noise reappeared. +#+begin_src emacs-lisp +(list :chats (hash-table-count telega--chats) + :errors (with-current-buffer "*Messages*" + (how-many "Telega error 404" (point-min) (point-max)))) +#+end_src +Expected: =:chats= is populated as before (non-zero, matching what a scan used to +report) and =:errors= is 0. Before the fix this reliably printed two per scan, so +a non-zero count means the callback isn't being consumed and the fix is wrong. + *** Sentry — entry gates fire with Craig present What we're verifying: the interactive entry gates stop for the right states and start the loop only on a clean, green baseline. - On rulesets (ratio), with a clean tree and green suite, say "start sentry hourly". @@ -599,6 +856,22 @@ From the roam inbox (2026-07-11): work in progress in one project shouldn't stop :END: The 2026-07-01 kb-hygiene report listed 42 agent KB nodes with no inbound id: links (of 53 agent nodes; 0 conflicts, no duplicate titles). Orphan-ness alone isn't a defect — agent nodes are found by rg, not only by links — but a periodic pass is worth doing: prune nodes that aged out, merge near-duplicates, add id: links where clusters exist. Regenerate the list with the kb-hygiene script rather than trusting the snapshot. Propose deletions/merges to Craig before applying (auto-cleanup allowed only for :agent:-tagged nodes after approval, per knowledge-base.md). +*** 2026-08-04 Tue @ 09:51:13 -0500 Fresh hygiene report — orphans 42 → 113 +The 2026-08-01 report: 124 agent nodes, 625 total KB org files, 0 conflicts, no +duplicate titles, and 113 orphans. So orphans grew 2.7× in a month while agent +nodes roughly doubled — nearly every node written since 2026-07-01 landed +unlinked. + +That reframes the task. At 42 this was a cleanup backlog; at 113, with the ratio +holding at about nine in ten, the backlog is a symptom and the write path is the +cause. Nodes are being written without =[[id:]]= links to their neighbours, so +pruning the list would leave the same list regrowing. Worth folding into the +pass: does =knowledge-base.md='s write block ask for the links firmly enough, +and would a node-write prompt to name one related node close it at the source? + +Grading unchanged at =[#C]= — orphan-ness still isn't a defect on its own, and +=rg= still finds these nodes. The number is a signal, not a failure. + ** TODO [#B] Helper-agent instance support — concurrent same-project Claude :feature:spec: :PROPERTIES: :CREATED: [2026-06-11 Thu] |
