Repository navigation
Task claims from muster and the upstream; a superseded PR-review route counts as handled - #586
Conversation
|
Round 0 (requirements & deletion) — Dispositions (operator): D1 kept, D5 kept and re-specced to agent/agent_session, D2 deleted, D3 deleted, D4 wired (an agent's own "open" releases its claim, as the docs say). |
…wins a supersede
Q1 (operator): an upstream claim must name a claimant. A status alone (an
upstream in_progress or paused, the owner's own card write, a plugin that sends
no claimants) is no claim. reference.claimants is re-specified to the upstream's
claim fields: [{agent, agent_session, name?}]; the card shows name, else agent,
else the session's prefix; a claimant naming none of the three is dropped.
Q2 (operator): only a review of the same or a newer head supersedes. muster
reads the pull request's commits (githubapp.App.PullRequestCommits, the
repository-limited pull-requests read) and compareHeads orders the heads: a late
review of an OLDER head supersedes nothing; a verified newer head writes
SupersededByNewerHeadError, the only spelling counted as handled; an unknown
order (no App, a force-pushed head, an error) supersedes by arrival as before
and still raises the "not acted on" ask (the #413 invariant, its test restored).
D2: notes.Claimed deleted. D3: ReleaseClaims takes no session. D4: a dispatched
agent setting its own task back to open (its route and its tool) releases the
task's claims.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Round 1 (nine axes) at 2aeff11, with its companion homelab-infra#1099 at 24721044: 1 🔴 (in the companion's claim rule), 3 🟡, 5 🟢 — the ladder continues. Two mutations re-run independently (every session link counts as a claim; head order flipped): both killed. Operator decisions on the findings: the upstream claim becomes a session-authored pickup (the latest status change into "in progress" made by an actor with a session; released when the status moves back to a not-started status or to done), replacing the post-create agent_session rule (R1, Y3). On muster's side a claim releases on done, an upstream reopen and the claiming session ending (Y1), and a claim only suppresses the pick-up card — the chief can still raise a question card for a claimed task (Y2). |
…wins a supersede
Q1 (operator): an upstream claim must name a claimant. A status alone (an
upstream in_progress or paused, the owner's own card write, a plugin that sends
no claimants) is no claim. reference.claimants is re-specified to the upstream's
claim fields: [{agent, agent_session, name?}]; the card shows name, else agent,
else the session's prefix; a claimant naming none of the three is dropped.
Q2 (operator): only a review of the same or a newer head supersedes. muster
reads the pull request's commits (githubapp.App.PullRequestCommits, the
repository-limited pull-requests read) and compareHeads orders the heads: a late
review of an OLDER head supersedes nothing; a verified newer head writes
SupersededByNewerHeadError, the only spelling counted as handled; an unknown
order (no App, a force-pushed head, an error) supersedes by arrival as before
and still raises the "not acted on" ask (the #413 invariant, its test restored).
D2: notes.Claimed deleted. D3: ReleaseClaims takes no session. D4: a dispatched
agent setting its own task back to open (its route and its tool) releases the
task's claims.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2aeff11 to
d3cd253
Compare
…ded session; a question is not a pickup Y1 (operator): a session claim is released (claimReleasedBy, on setNoteStatus, the funnel every writer passes) by an open from a person, session or agent (stored or queued), by the upstream reopening a reference (a stored snapshot open), and by complete stored by any writer; and when the claiming session ENDED (sessionEnded: its window gone from the host it claimed from while that host is online, reports status and lists windows — muster's EndGone rule). The ended check runs at the triage and task_route_attention before they read a claim, and in a claim sweep every minute (RunClaimSweep; one system-scoped read of the claims, each judged and released as its owner). The claim's host is now the mirror's host when one of the owner's hosts lists the session. Y2 (operator): a claim suppresses only the pick-up card. task_route_attention takes an explicit `question`; with one, the card is raised on a claimed task, naming the question and who is working it. An upstream claimant must carry a valid session (agent_session): an agent or name alone is no claim (round 1: a claim is a session's pickup). D4: an agent's open releases every session claim on its task (documented: an agent names no session, so none is its own). 0111's header no longer says a status is a claim. Rebased on origin/main (#588); 0111 is still the next free migration. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Round 2 (delta Operator decision on Y-a: a hand-off releases — the plugin releases a claim when the claiming agent is removed from the task's assignees, and muster releases an upstream claim whose session it sees ended on its host. Y-a, Y-b and the first 🟢 are fixed in a final commit (not re-audited); the rest recorded open. |
…e counts as handled
Claims (migration 0111, internal/notes/claims.go, internal/api/task_claims.go):
- notes.Claims is THE predicate: a session's explicit claim (task_sessions.claimed_at,
set when a status write to in_progress carries the session's id: the machine route
with X-Muster-Session-Id, or a mirrored session's task_set_status), or a plugin
task reference whose snapshot names claimants or is in_progress.
- reference.claimants (optional, cut never refused): [{kind, name, session}]; a
claimant whose session a host of the owner lists is linked to the task (worked).
- The task triage leaves an assignment of a claimed task alone (task_claimed: no
card, no chief turn; the clean forward to a bound agent still wins), and the
chief's task_route_attention raises no card for one.
- The board card shows "worked by <who>" (data-task-claimed names the sources).
- An unclaim: a person's, session's or agent's status write to open releases every
session claim; a snapshot without claimants and not in progress drops the
upstream's. A status the upstream refuses (queued, then failed) does not take
the claim with it.
Superseded review routes (internal/plugins/unrouted.go):
- A message whose route failed ONLY because a newer review round of the same pull
request superseded it (SupersededRouteError naming another message's route) is
dispositioned: no "not acted on" ask, and an open one is closed. The newer
round's message raises the ask when its own route fails for a real reason.
TestALateOlderRoundDoesNotSettleAQueuedNewerRound's ask assertion is inverted
accordingly (operator decision).
Docs: tasks.md#claims, plugins.md (reference.claimants, the triage table, the
superseded-route rules), usage.md (task_claimed).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…wins a supersede
Q1 (operator): an upstream claim must name a claimant. A status alone (an
upstream in_progress or paused, the owner's own card write, a plugin that sends
no claimants) is no claim. reference.claimants is re-specified to the upstream's
claim fields: [{agent, agent_session, name?}]; the card shows name, else agent,
else the session's prefix; a claimant naming none of the three is dropped.
Q2 (operator): only a review of the same or a newer head supersedes. muster
reads the pull request's commits (githubapp.App.PullRequestCommits, the
repository-limited pull-requests read) and compareHeads orders the heads: a late
review of an OLDER head supersedes nothing; a verified newer head writes
SupersededByNewerHeadError, the only spelling counted as handled; an unknown
order (no App, a force-pushed head, an error) supersedes by arrival as before
and still raises the "not acted on" ask (the #413 invariant, its test restored).
D2: notes.Claimed deleted. D3: ReleaseClaims takes no session. D4: a dispatched
agent setting its own task back to open (its route and its tool) releases the
task's claims.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…te change, never the filer's stamp Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ded session; a question is not a pickup Y1 (operator): a session claim is released (claimReleasedBy, on setNoteStatus, the funnel every writer passes) by an open from a person, session or agent (stored or queued), by the upstream reopening a reference (a stored snapshot open), and by complete stored by any writer; and when the claiming session ENDED (sessionEnded: its window gone from the host it claimed from while that host is online, reports status and lists windows — muster's EndGone rule). The ended check runs at the triage and task_route_attention before they read a claim, and in a claim sweep every minute (RunClaimSweep; one system-scoped read of the claims, each judged and released as its owner). The claim's host is now the mirror's host when one of the owner's hosts lists the session. Y2 (operator): a claim suppresses only the pick-up card. task_route_attention takes an explicit `question`; with one, the card is raised on a claimed task, naming the question and who is working it. An upstream claimant must carry a valid session (agent_session): an agent or name alone is no claim (round 1: a claim is a session's pickup). D4: an agent's open releases every session claim on its task (documented: an agent names no session, so none is its own). 0111's header no longer says a status is a claim. Rebased on origin/main (#588); 0111 is still the next free migration. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…st is the mirror's; no dispatch on a claimed task Y-a (operator): an upstream claim whose claimant session ended (its window gone from the host the mirror listed it on, the sessionEnded rule) is released, at the triage, task_route_attention and the claim sweep. The release is recorded on the claimant's task link per snapshot revision (task_sessions. upstream_released_rev, "rev:<rev>"), so the next snapshot still naming that session at the same revision does not bring it back; a newer revision is judged afresh. A claimant whose session no host listed is never judged ended. Y-b: a claim's host (task_sessions.claim_host, new in 0111) is recorded only when the mirror lists the claiming session; the request's host header is no longer a fallback, and ended is judged on claim_host only (never task_sessions.host). G: a claimed task starts no new agent: chiefDispatchTaskBlock (the one predicate behind the card's offer and agent_dispatch's gate) refuses it, so a question card on a claimed task offers no agent_dispatch button. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
d3cd253 to
fcedc24
Compare
Two fixes in one PR.
1. Claims
The predicate:
notes.Claims(internal/notes/claims.go) is the only definition. The triage, the chief'stask_route_attentionand the board all read it.in_progressthat carries the session's id claims the task. That coversPATCH /api/tasks/{id}/statuswithX-Muster-Session-Id(whatmuster task status <id> in_progresssends from inside a session) and a mirrored session's owntask_set_status.task_sessionslink becomesworkedand getsclaimed_atandclaimed_by(migration0111).claimed_byis the mirrored session's name when a host of the owner lists it, otherwisesession <id prefix>.claimants, or whose status isin_progress, is claimed.upstream (<label>).sessionis a mirrored session that one of the owner's hosts lists now is linked to the task asworked.task_claimedoutcome. The assignment is settled asmuster: task triage — left it alone: it is already claimed by <who>, with no card and no chief turn. The one case that still goes ahead is a clean forward to the single agent bound to the task.task_route_attentionon a claimed task raises no card and answersclaimed: truewithclaimed_by. Either way the task comes off "Task to route".⚑ worked by <who>.data-task-claimednames the sources.openby a person (the card), a session or machine (the machine route) or an agent (the task tools) releases every session claim.What the HQ plugin actually sends today
I read the plugin's source (its
refs.goandrefclients.go), which lives outside this repo.title,url,status,status_label,rev,updated_at,body,comments(author, plus(agent <id>)when an agent wrote it) anddetach, pluscreatorbehind a flag.statusis the HQ status mapped onto muster's four. "in progress" and "paused" becomein_progress, and "to discuss" becomesopen.status_labelis the raw HQ status.assignees, but only to decidedetach(the owner is no longer assigned). It never reads HQ'sagent_assignees, which is what an agent's "assign me" adds.Proposed minimal contract change (documented in
docs/plugins.md): an optionalreference.claimants: [{"kind": "person"|"agent", "name": "…", "session": "…"}].agent_assignees, plus anyassigneesother than the owner, resolved to display names.kindother than person or agent, and asessionthat is not a session id. At most 20 claimants are kept.400to it, the same waycreatorhas to be gated.The "to discuss" in_progress that never applied
On a reference, an owner's
in_progressis queued for the plugin. The plugin maps it to the HQ status "in progress". A record whose type has no such status (a discussion record: proposal, to discuss, discussed, processed, wontfix) makes the plugin fail the write definitively: "the HQ task's type has no status "in progress" … not written".A reference's status only moves when a snapshot arrives, so the task stays
open. The write shows as failed on the task page.Fix here: the claim does not depend on the status at all. It is recorded on the session's link whether the status was stored, queued or later refused.
TestAClaimSurvivesAnUpstreamThatRefusesInProgresscovers it: a pickup, then the plugin fails the write and pushes anopensnapshot, and the task is still claimed and the triage still leaves it alone.Not changed: the owner's own card write with no session is not a claim, because it names no claimant. Mapping a discussion record's "in progress" is the plugin's job.
2. Superseded review routes
supersededByNewerRound(internal/plugins/unrouted.go) is added todispositioned. A route that isfailedwith exactlySupersededRouteError(<route id>)is handled, as long as the named route exists and belongs to another message.supersedePRRoutes) and the SQL pattern both useplugins.PRRouteSupersededandSupersededRouteError.⚠ This reverses an invariant from the audit of #413.
TestALateOlderRoundDoesNotSettleAQueuedNewerRoundasserted the ask fires for an unsent superseded route. Supersede orders rounds by arrival, so a late review of an older head can supersede a newer head's unsent route. The newer head's review then reaches no one, and nothing asks about it. That assertion is inverted here to match the operator's decision, and the tradeoff is written down in the test and indocs/plugins.md.Red/green matrix (base = origin/main at the time of the run, each test run on its own database)
claimantsrefused, 400)claimants: [], 400)task-claims.spec.tsat 390×844 and 1280×800[data-task-claimed])Pure unit tests of new code don't compile on the base tree, so they have no red column:
TestClaimsPinnedCases, the new claimed rows ofTestDecideTaskTriagePinnedCases, andTestAReferencesClaimantsAreCutNeverRefused.Mutation sweep
Each mutant patches one expression, runs the named tests, and the file is then restored byte for byte. A test only counts as the killer if it is that mutant's own target test.
task_route_attentionguard removedM12 and M13 survived the first sweep. The ghost-route subtest had no other message's route in the database, and M13's run did not include the newest-message test. I fixed the fixture and the harness, and both mutants were then killed.
Ledgers touched
broadcast_ledger_test.go:claimOnStatus.names_ledger_test.go:knownMirrorSession, a board chip rather than a notification.TestSessionColsAndScanTargetsAgree: the projection gainedclaimed_atandclaimed_by.e2e/legs.json: the new spec is in leg 2 at an estimated 4 s.🤖 Generated with Claude Code