Skip to content

Tasks: the older task road refuses duplicate work and holds a tree spend total - #2464

Merged
AbirAbbas merged 4 commits into
workstreams/mainfrom
workstreams/l-fix9-e3
Nov 6, 2026
Merged

AbirAbbas merged 4 commits into
workstreams/mainfrom
workstreams/l-fix9-e3

Conversation

@AbirAbbas

@AbirAbbas AbirAbbas commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #2446. With CODEAF_TASK_BELT=node (the older session-tree road, still a live escape hatch), tasks had depth 3, 20 children per node and a per-node bill limit, but no duplicate-title refusal and no spend total for the tree. This gives that road the same bounds #2446 gave the default road, reusing its code.

  • Duplicate work is refused tree-wide. At the one admission door every older-road child passes (claimChild: proposals, quick tasks, divisions, plan dispatch), a title equal to any non-cancelled task in the same tree after plandb's normalization is refused with Tasks: a worker does its own task, and a task tree is bounded in depth, count and spend #2446's words, and nothing starts. A division that names the same work twice is refused whole. Pending proposals reserve their titles. plandb.NormalizedTitle exports the existing normalization so both roads share one definition. A typed task starts its own tree, so separate trees may reuse a title.
  • A tree spend total. The older road already refuses a worker's next request when its own node's bill reaches the per-node limit. Beside that check, the tree's summed bill (the root and every descendant node, each node's own bill, which includes its workers', repairs', checker's and paid tool receipts) is compared with the tree's ceiling. The ceiling is fixed when the root is admitted, using Tasks: a worker does its own task, and a task tree is bounded in depth, count and spend #2446's arithmetic (the lower of crew.task_cap, $5 by default, and the conversation's remaining limit, factored into taskTreeCost), and kept in the checkpoint as an additive fact, so a restore cannot re-grant it. A node over it ends on the cost-limit ending with the configured-limit words, or the conversation-limit words when that limit bound it. A node in flight ends at its next request, as with the per-node limit; a node that starts later spends nothing. No new guard, stop or cancellation path.
  • The default road is unchanged: the new title gate and tally are off there.

Validation contract → tests (internal/session/task_node_bounds_test.go)

Contract Test
C1 duplicates anywhere in the tree refused; cancelled titles reusable; different titles still start TestNodeTreeRefusesDuplicateWorkAnywhere, TestNodeTreeAllowsCancelledTitlesAndSeparateRoots, TestNodeTreeReservesPendingProposalTitles
C2 a batch naming the same work twice is refused whole TestNodeTreeRefusesAnEntireDuplicateDivisionAndQuickTask
C3 separate trees may share a title and keep their own totals TestNodeTreeAllowsCancelledTitlesAndSeparateRoots, TestNodeTreeSpendStopsEveryNodeAndNamesTheBindingLimit
C4 the tree total stops every node of the tree and names the binding limit; restores keep the ceiling TestNodeTreeSpendStopsEveryNodeAndNamesTheBindingLimit, TestNodeTreeCheckpointKeepsTheUnspentAllowance, TestNodeTreeCheckpointWithoutLimitComputesItAtRestore, TestNodeTreeWithNoConversationAllowanceSpendsNothing, TestNodeTreeLimitRefusesBeforeCheckpointWrites
C5 a node under the ceiling finishes as before TestNodeTreeUnderTheCeilingFinishesNormally
C6 one normalization for both roads TestNodeTreeUsesThePlanStoresWorkIdentity

Existing tests changed

All three used repeated titles as filler, which C1/C2 now refuse on purpose. Their fixtures name distinct work; every assertion is unchanged.

  • TestADivisionWiderThanTheCapIsRefusedWholeRatherThanHalfTaken: fills the fan cap with 18 distinct parts instead of repeating two titles, and asserts that setup count.
  • TestAThirdAskAfterARepairNeitherRefusesNorDoublesTheFamilysChecks: the third ask names ranking/browsing instead of re-sending rank/browse.
  • TestAPartsBriefHoldsItsBoundWhateverLengthTheFieldsArriveAt ("every field at once"): its two parts had the same full-length title; the second now starts "the beta adapter " and every field stays at full length.

Review round

A fresh review of the first version found five majors, all in its separate shared spend guard (a limit stop read as a person's stop, paid tool receipts missed, a restore could re-grant a conversation-bound allowance, cancellation waited on checkpoint writes, a racing test). The tree total was redone as the check above, which has none of those paths.

🤖 Generated with Claude Code

AbirAbbas added a commit that referenced this pull request Nov 6, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Nov 6, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas force-pushed the workstreams/l-fix9-e3 branch from 7b12565 to b72db24 Compare November 6, 2026 14:39
AbirAbbas and others added 4 commits November 6, 2026 09:55
…end total

With CODEAF_TASK_BELT=node the session-tree road bounded depth (3), children per
node (20) and each node's own bill, but two nodes of one tree could hand out the
same work, and the tree as a whole had no spend total. It now applies the default
road's bounds from #2446:

- A child whose title matches, after plandb's normalization (now exported as
  plandb.NormalizedTitle, one definition for both roads), any non-cancelled task
  in the same tree is refused with the same words, and a division naming the same
  work twice is refused whole. Pending proposals reserve their titles. A typed
  task starts a separate tree, so separate trees may reuse a title.
- Every node, helper and check of one tree shares one spend tally held to the
  lower of crew.task_cap and the conversation's remaining limit (the same
  arithmetic as the default road). Reaching it stops the whole tree and the
  ending names the limit and how to raise it. A checkpoint keeps the tree's spent
  total.

Two existing division tests used repeated titles as filler; their fixtures now
name distinct work and keep every assertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round: the first version held the tree to a new shared guard with its
own stop and cancellation, which read a limit stop as a person's stop, missed
paid tool receipts, could re-grant a conversation-bound allowance after a
restore, and waited on checkpoint writes before cancelling. The tree total now
reuses the older road's existing per-node check instead: before each worker
request, the summed bill of the tree's root and every descendant is compared
with the tree's ceiling, which is fixed when the root is admitted (the default
road's arithmetic) and kept in the checkpoint. A node over it ends on the
cost-limit ending with the configured-limit words, or the conversation-limit
words when that bound it. The shared guard and its hooks are gone.

TestAPartsBriefHoldsItsBoundWhateverLengthTheFieldsArriveAt sent two parts with
the same title, which a division now refuses; its second part's title differs
and every field stays at full length.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lan specs an id

The spend test's graph dropped every report, so the root never finished folding
its parts; it now delivers child reports as production does. The work-identity
test's plan write lacked an id, so plandb refused it before comparing titles.
No production change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas force-pushed the workstreams/l-fix9-e3 branch from b72db24 to f9b3471 Compare November 6, 2026 14:55
@AbirAbbas
AbirAbbas merged commit e30414b into workstreams/main Nov 6, 2026
7 checks passed
@AbirAbbas
AbirAbbas deleted the workstreams/l-fix9-e3 branch November 6, 2026 15:02
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