Skip to content

feat(loop): track loop progress in durable chat tasks, not PROGRESS.md - #1119

Merged
cuongtranba merged 1 commit into
mainfrom
feat/loop-tasks-tracking
Sep 18, 2026
Merged

cuongtranba merged 1 commit into
mainfrom
feat/loop-tasks-tracking

Conversation

@cuongtranba

Copy link
Copy Markdown
Owner

What

An autonomous loop's plan now lives in event-sourced per-chat task state, read and written through the mcp__kanna__task_* tools, instead of a markdown tracking file. A loop no longer creates, reads, or writes PROGRESS.md.

Why this could not be a prompt change

The loop /clears main on every wake. That nulls the session token — so the next turn is a brand-new CLI session whose own task list is empty and TaskList returns nothing — and it tripped the client reducer's wipe on context_cleared. Three more blockers stacked on top:

  • worker subagents write to subagent_entry_appended on the run, never the parent chat's transcript;
  • the PTY driver passes no --tools, so it had no native Task tools at all;
  • the card only folded the loaded transcript page, so a long loop's task_create scrolled out of the window.

Underneath all four: there was no server-side task state whatsoever.

How

  • Durable storesrc/shared/chat-tasks/** (types, pure scheduler, projection, compaction, native mirror, seed, tracking-file import) on a new chat-tasks event log, folded into StoreState and the snapshot. Survives /clear, restart, and the tail window.
  • mcp__kanna__task_* — create/update/list/get/note/claim/settle/integrate, registered wherever a chatId is present, so a worker subagent's writes land on the parent chat and PTY gets a working surface.
  • Native CLI Task calls are mirrored, not blocked, under an n: id namespace beside Kanna-minted k:. On context_cleared native tasks are marked stale, not deleted — the CLI really did forget them, but deleting loses visible history.
  • Both loop prompts, both invariant lists, setup_loop, the wake prompt and the Progress card move onto tasks. The wake now carries a rendered <loop-state> snapshot. setup_loop seeds from a tasks argument or chunk_hint, and imports an existing tracking file once.
  • One unified footer cardTaskProgressSection merged into LoopProgressSection; titled "Progress" when armed, "Tasks" otherwise.

Bugs found and fixed along the way

  1. The claim was not actually atomic. EventStore.append applies state after the disk write, so two concurrent claims could both read stale state and take the same task. Fixed with decideChatTaskEvents, which runs the read-decide-append inside the per-chat write chain. Verified the regression test catches it — reverting the fix makes both workers get the same task id.
  2. An unconditional await I added to appendMessage — the hottest write path — destabilised a timing test. Now conditional on there being events to append.
  3. Pre-existing: resume_loop dropped parallelism, silently demoting a resumed parallel loop to serial.

Removed

claim_tracking_task / complete_tracking_task / integrate_tracking_tasks, shared/loop-task-queue.ts, the loop-tracking registry / sync / io adapters, and the file-based reconcile and skeleton. query_tracking_file, append_tracking_row and replace_tracking_section stay for the non-loop durable-document use CLAUDE.md documents.

Net −2300 lines.

Budgets

  • deps-bundles re-baselined 85 → 83 (TaskQueueToolDeps and its adapters deleted; ChatTaskToolDeps added).
  • max-params lowered 12 → 11 in both eslint.config.js and ESLINT_LIMIT_PINS — removing getLoopTracking took deriveChatSnapshot to 11, and lint:limits requires the ceiling stay tight.

Verification

bun run check, bun run test (8260 pass, 0 fail), lint:comments, lint:usestate, lint:limits, check:arch, bunx ast-grep test, check:commits — all green locally.

Follow-up needed

c3x is not installed in this environment and a c3-seal cannot be computed by hand, so the ADR (adr-20260918-loop-tasks-replace-tracking-file, referenced in CLAUDE.md and the skill) and the .c3/ component bindings for the new modules still need a c3x add adr + c3x repair pass. The deleted loop-task-queue.ts binding was removed from the unsealed code-map.yaml / eval/c3-210.yaml.

An autonomous loop's plan now lives in event-sourced per-chat task state, read
and written through the mcp__kanna__task_* tools, instead of a markdown tracking
file. The loop /clears main on every wake, which nulls the session token and
empties the CLI's own task list, so neither the native Task tools nor the
client-side task card could carry a plan across iterations.

Adds a chat-tasks event log folded into StoreState and the snapshot, so task
state survives /clear, a server restart and the transcript tail window. Native
CLI Task calls are mirrored under an n: id namespace alongside Kanna-minted k:
ids; on context_cleared the native ones are marked stale rather than deleted.

The task tools are registered wherever a chatId is present, so a worker
subagent's writes land on the parent chat, and the PTY driver gets a working
task surface for the first time.

Claiming is atomic: EventStore.decideChatTaskEvents runs the read-decide-append
inside the per-chat write chain, replacing the old withFileLock
read-modify-write. Leases are still reclaimed on run liveness, never a clock.

Both loop prompts, both invariant lists, setup_loop, the wake prompt and the
Progress card move onto tasks. setup_loop seeds from a tasks argument or
chunk_hint and imports an existing tracking file once, writing no file itself.

Also fixes a pre-existing bug where resume_loop dropped parallelism, silently
demoting a resumed parallel loop to serial.

Removes the file-based loop tracking: the claim, complete and integrate tracking
task tools, loop-task-queue.ts, the loop-tracking registry, sync and io
adapters, and the file reconcile and skeleton. query_tracking_file,
append_tracking_row and replace_tracking_section stay for the non-loop
durable-document use.
@cuongtranba
cuongtranba merged commit a31bb47 into main Sep 18, 2026
5 checks passed
@cuongtranba
cuongtranba deleted the feat/loop-tasks-tracking branch September 18, 2026 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant