feat(autonomy): durable task comments and the enrichment loop - #639
Draft
jamiepine wants to merge 2 commits into
Draft
feat(autonomy): durable task comments and the enrichment loop#639jamiepine wants to merge 2 commits into
jamiepine wants to merge 2 commits into
Conversation
The autonomy channel had a survey and a run summary but nothing to write
findings into, and no task tools registered at all — the enrichment loop
described in autonomy.md was prose, not code. This lands the missing half.
Task comments
- New `task_comments` table (append-only, `seq`-ordered so pagination is
stable inside a millisecond), with author type/id, optional worker link,
and metadata. `TaskStore::delete` clears comments in the same transaction
rather than relying on a cascade that only fires with `PRAGMA foreign_keys`.
- `GET`/`POST /tasks/{n}/comments`, generated OpenAPI + TS client, and a
comment thread on task detail in both task routes. Worker-linked comments
render a pill that expands the worker's output on demand.
Enrichment cadence
- `last_enriched_at` on tasks, stamped in the same transaction as the comment
that earned it. User comments deliberately do not move it.
- `TaskStore::select_for_enrichment` orders candidates in SQL: user-engaged
since last enrichment, then never enriched, then stale — with last run's
work excluded unless a user has since commented. Rendered as an Enrichment
Queue in the wake briefing, comments inlined.
- `max_tasks_per_run` is now enforced at the tool boundary via a per-run
budget instead of being a request in the prompt.
Claiming
- `add_task_comment` claims an unassigned task atomically before writing when
`claim_unowned` is set; the loser of a race gets a clean skip. Listing
claims nothing, and `observe` gets no mutation tools to claim with.
- `claim_next_ready` takes assignment and `in_progress` in one guarded UPDATE
and can pick up unowned ready work.
Worker continuity
- Ready-task pickup appends the task's comments to the worker prompt, bounded
per comment and in total. `WorkerContextMode::Briefed` does not exist in
source; the injection landed where a worker is actually bound to a task.
Goals
- `task_create` takes `goal_id` through tool, store, and API, resolving it
against the goal store first so an unknown id is an error, not a dangling
link.
- Autonomy gets `goal_update` with the `status` field removed from the schema
entirely, and each run programmatically flags active goals whose linked
tasks are all done with a ready-for-review note. Goals never auto-complete.
Docs: autonomy.md and goals.md now describe what ships. "Quiet while active"
is marked rejected, with the reasoning, rather than left on the phase list.
… enrichment The user-engaged check compared a comment's created_at strictly greater than last_enriched_at. Both are millisecond-precision, so a user comment landing in the same millisecond as the enrichment that preceded it was dropped from the selection and the task fell out of the queue. Comparison is now inclusive: erring toward one extra look is the safe direction, erring toward losing a user's input is not. Test fixtures move from a shared-cache in-memory database to a file-backed one in a temp dir. Shared-cache SQLite raises SQLITE_LOCKED on contention, which busy_timeout does not retry, so the concurrency tests were flaking on an artifact no deployment can hit. The file DB is also what the instance store actually runs on. The concurrent-claim assertion was too narrow: the loser can lose at either gate — reading the assignment the winner committed, or losing the guarded UPDATE — and both correctly end in a skip with nothing written.
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #631 (autonomy channel/goals/wakes/run-history shell). The channel had a survey and a run summary but nothing to write findings into — and no task tools registered at all. The enrichment loop in
autonomy.mdwas prose. This lands the code.Task comments
New
task_commentstable, append-only.seq INTEGER PRIMARY KEY AUTOINCREMENTis the ordering key rather thancreated_at: two comments in the same millisecond would otherwise have no defined order and pagination cursors would skip or repeat rows.seqis also the cursor the list endpoint returns.TaskStore::deleteclears a task's comments in the same transaction as the task.ON DELETE CASCADEis declared too, but it only fires when the connection hasPRAGMA foreign_keyson — a setting the store doesn't own.GET/POST /tasks/{n}/comments, regenerated OpenAPI + TS client, and a comment thread on task detail in both task routes. A worker-linked comment renders a pill that fetches that worker's output on expand — the body stays the agent's summary, transcripts never inline.Enrichment cadence
last_enriched_aton tasks, stamped in the same transaction as the comment that earned it, so a crash can't mark a task enriched without the finding that enriched it. User comments deliberately don't move it — being commented on is what pulls a task back to the front.TaskStore::select_for_enrichmentdoes the ordering in SQL: user-engaged since last enrichment, then never enriched, then stale, with last run's work excluded unless a user has since commented. It renders into the wake briefing as an Enrichment Queue with each entry's recent comments inlined, so the run opens on a list that already excludes what it just did.max_tasks_per_runis enforced at the tool boundary now instead of being a request in the prompt:Claiming
add_task_commentclaims an unassigned task atomically before writing whenclaim_unownedis set. Listing claims nothing.observegets no mutation tools registered at all, so it can't claim by any path.claim_next_readynow takes assignment andin_progressin one guarded UPDATE and can pick up unowned ready work.Worker continuity
Ready-task pickup appends the task's comments to the worker prompt — oldest first, 12 comments, 600 bytes each, 4000 total.
WorkerContextMode::Briefeddoes not exist in source.WorkerContextModeis a struct ofhistory/memory/wiki_write; the enum inworker-briefing.mdwas never built. Comments are injected at ready-task pickup instead, which is the only place a worker is actually bound to a task and therefore the only place the briefing has comments to carry. Flagged rather than silently reinterpreted.Goals
task_createtakesgoal_idthrough tool, store and API, resolving it against the goal store first so an unknown id is a tool error rather than a dangling link.Autonomy gets
goal_updatewith thestatusfield removed from the schema, not just discouraged in prose. Each run then programmatically flags active goals whose linked tasks are alldone:Prepended to the goal's notes, keeping the agent's own assessment beneath it. Idempotent, treats
failedas work remaining, never touches status. Goals never auto-complete.Quiet-while-active: rejected
Marked rejected in
autonomy.mdwith the reasoning, not left on the phase list. Enrichment is most useful while the user is around to react to it, and the wake already carries the signal that matters — a user comment pulls the next run forward rather than pushing it away. A global "someone is talking, stand down" flag suppresses the run for reasons unrelated to the task it was going to work on.Testing
23 new tests. Adversarial ones:
concurrent_claims_produce_one_winner/concurrent_first_comments_leave_one_winner— two agents race the same unowned task; exactly one wins, the loser gets a skip with nothing writtenselection_excludes_work_from_the_previous_run— and that a user comment clears the exclusionselection_orders_user_engaged_then_never_enriched_then_stale,selection_respects_assignment_and_claim_unownedcomment_rejects_another_agents_task— wrong-agent accessbudget_caps_distinct_tasks_per_runagent_comment_stamps_enrichment_and_user_comment_does_notgoals_are_flagged_for_review_only_when_every_linked_task_is_done— partial, zero-task and failed-task goals stay unflagged; re-running doesn't stack markersdeleting_a_task_removes_its_comments,create_rejects_an_unknown_goal_linkTwo of these were flaky on first write and the second commit fixes the causes, one of which was a real bug:
>againstlast_enriched_at. Both are millisecond-precision, so a user comment landing in the same millisecond as the preceding enrichment was silently dropped from selection. Now inclusive.SQLITE_LOCKEDon contention, whichbusy_timeoutdoesn't retry — the concurrency tests were flaking on an artifact no deployment can hit.Validation state:
cargo fmt --all --check,cargo check --all-targets,RUSTFLAGS=-Dwarnings cargo clippy --all-targets,cargo test --lib(1169 passed),just check-typegen(generated schema in sync),bunx tsc --noEmitandbun run buildall pass. Migration safety passed — only new migration files. The finalcargo test --tests --no-runstep ofgate-pr.shwas still running when I cut the PR; it passed on an earlier identical run of the gate.🤖 Generated with Claude Code
Note
AI Summary: This PR implements the autonomy enrichment loop with durable task comments. Key additions: a new
task_commentstable with sequence-based ordering to prevent pagination issues, enrichment selection logic that prioritizes user-engaged tasks and excludes recent work, atomic task claiming for racing agents, goal review flagging when all linked tasks complete, and 23 new adversarial tests covering concurrency, budget enforcement, and access control. Two follow-up commits fix a millisecond-precision race condition in the user-engagement check and flaky concurrency tests by switching to file-backed SQLite. All validation checks pass (cargo fmt, clippy, 1169 lib tests, generated schema sync).Written by Tembo for commit 1ac3b5ad.