Skip to content

design(queue): durable task data model — blocker links, park revalidation, status reasons + notes, full event history, actor provenance #3424

Open

Description

@dezren39

Direction (Drew, 2026-11-01 ~02:40 CST)

Make the repo-queue data model durable — "make this system to last." Six pieces, one design:

1. External waits take a real datetime

Today park accepts only --delay-seconds; the not_before column is a timestamp and enqueue already accepts an absolute datetime (helper example: 2026-10-25T12:00:00Z), but a mid-work park cannot name one. External events have known instants (a quota reset, a review window): park should accept an absolute not_before datetime as well as a relative delay.

2. Blockers are links, not text

A task's blockers are recorded as typed links — to other queue tasks (preferred: they are queryable, so claim can exclude a task whose blocker task is incomplete, and completing a blocker can surface its dependents), and to PRs and issues. Today block stores a free-text reason in progress/last_error; nothing can query it, so blocked-on relationships are invisible to the claim query.

3. Long parks get re-verified on a schedule

A maintenance check re-verifies every long park per item on a cadence (default daily): are the linked blockers still real, has anything changed around them that the time, the queue state, or the GitHub number doesn't capture? The check writes last_checked_at and its outcome onto the item; a park confirmed still-valid stays parked untouched; a stale park is flagged with a note (piece 4) for pickup or release.

4. Status reason column + a notes table, mirrored out

Every status transition records a status_reason, which automatically lands as a row in a notes/comments table (task, actor, kind — status_reason / finding / handoff — body, timestamp). Findings and notes from previous pickups, and insights from related items, accumulate there instead of evaporating between shifts. The system comments notes back automatically (to the linked GitHub issue) so the record lives where the work is discussed.

5. Full event history — resurrect any moment

An append-only events table records every change to a task (status, priority, park, claim, links, notes) with before/after values and the actor. Any past state of any task is reconstructable from the event stream.

6. Actor provenance on every write

Every create/update records who made it: lane, runtime (opencode / Muse / whichever system), session id, hostname, pid, ip address, task id and the task's start, and — at creation — which system found/filed the originating issue. Provenance rides on tasks, notes, and events alike.

Migrations question (part of the design's scope)

Schema changes for this system should land as proper versioned migrations, in order, in a repo — the design must first inventory how the muse-dev Supabase schema (agent schema: tasks, claim/park RPCs) is currently changed, and establish the migration discipline before adding pieces 1–6.

Acceptance

  • Design record covering pieces 1–6: schemas for blocker links, notes, events; the park-datetime change; the revalidation check's cadence/config and where it runs; provenance field set.
  • Migration discipline decided and the current schema state inventoried as the baseline migration.
  • Implementation packets split per piece and enqueued, each independently landable.

Related: #3418 (budget-aware claiming — the claim-side companion; blocker links and budget requirements both belong in the claim query), opencode#1014 task 5256 (the capacity-wait park that started this line of design).

Activity

  1. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Design amendment from Drew, 2026-11-01 ~03:04 CST — notes typing/tagging and a standard link classification with inference. Extends pieces 2 and 4 of this issue.

    Notes and items carry types and tags

    • Notes (piece 4) have a TYPE (status_reason, finding, handoff, … — an extensible controlled vocabulary, not free text) plus free-form TAGS.
    • Queue items likewise carry a type beyond the current kind, plus tags, so queries and views can slice by either.

    Links have three axes

    Every link between items (queue tasks, issues, PRs) records:

    1. Kind — from the standard classification below.
    2. Provenance — direct (a human/agent added it), inferred (the system derived it, rules below), or related (system-suggested association, weakest).
    3. Blocking semantics — derived from the kind by conversion rules: hard_block (claim must exclude the dependent), soft_block (ordering preference only — ideally after, never a stop), or none. The claim query consumes semantics, never raw kinds.

    Standard link classification (v1 vocabulary)

    Start from the Jira link set (relates-to, blocks/is-blocked-by, duplicates/is-duplicated-by, clones, causes/is-caused-by) and extend:

    • blocks → converts to hard_block
    • ideally_after → converts to soft_block
    • related → no block
    • handled_together — convergent/associated: would be good handled together or under one design (includes a needs_design_together flavor)
    • causes / caused_by
    • duplicate / overlap — overlap is the partial case (shared scope, neither contains the other)
    • xor / conflicts — either/or: doing one forecloses or collides with the other

    Semantics of the soft kinds (handled_together, ideally_after, overlap) are deliberately NOT pinned yet — Drew: "can figure out what to make that mean later." The vocabulary, provenance, and conversion-to-blocking layer land now; the soft-kind behaviors are an explicit design TBD, not a blocker for the rest.

    Inference rules (system-added links, always marked inferred)

    • If issue Update Rust crate tokio to v1.38.2 [SECURITY] - autoclosed #123 is added as a blocker and queue item 1024 represents Update Rust crate tokio to v1.38.2 [SECURITY] - autoclosed #123, the system adds an inferred blocker link to 1024 as well (the queue item is the queryable form of the same blocker).
    • If a PR is opened that closes a blocked-on issue, the PR is linked into the chain as inferred (so shepherds and the claim query can see the resolving artifact).
    • Inference never silently upgrades semantics: an inferred link carries the kind it was derived from, and its blocking effect follows the same conversion rules; inferred links are visually and structurally distinguishable from direct ones everywhere they surface.
  2. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Clarification from Drew, 2026-11-01 ~03:09 CST, binding on how to read the link-classification amendment above: his examples are DIRECTION, not specification. The kind / derived-effect / provenance layering, the vocabulary names, and the inference examples sketch the kind of thing the model should have — where exactly the line sits between a link kind and a derived effect is itself an open design question for this issue to settle. What is firm: blockers must be queryable, blocking behavior must be derivable rather than hand-maintained, system-added links must be honestly distinguished from direct ones, and notes/items are typed and tagged. The design owns the final model; do not implement the sketch literally.

  3. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Further clarification from Drew, 2026-11-01 ~03:10 CST: the direct / inferred / related provenance split is itself NOT fixed either. The design may choose different categories, restructure the axis, or add subfields — whatever fits the model best. Same status as the kind/effect line: illustrative, open to redesign; only the underlying intent (system-derived relationships must be honestly distinguishable from deliberately-added ones, in some form) is firm.

  4. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Structural note from Drew, 2026-11-01 ~03:11 CST: links are mostly POINT-TO-POINT (pairwise between two items) — model them that way rather than inventing group/graph constructs the use cases don't need. And the schema should follow NORMAL FORM: break out into multiple proper tables as appropriate (links, notes, events, tags, link types as their own relations where that fits) instead of JSON blobs on the task row. Same status as the other notes: sound default structure, design may adjust where it can justify it.

  5. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Storage-shape ruling (Drew asked for a judgment call, 2026-11-01 ~03:13 CST): NORMAL FORM is the default and is right for everything the system queries — links, notes, events, tags, blockers, provenance keys. JSONB is the most appropriate tool in exactly four pockets, and is sanctioned there: (1) the task PAYLOAD/packet — an opaque, versioned document (repo-task/v1) authored and consumed whole, never queried by field; (2) event DIFF/snapshot payloads — each event is a proper row (type, actor, timestamp as columns) carrying its before/after as JSONB, since normalizing arbitrary field changes makes reconstruction queries worse, not better; (3) a provenance EXTRAS bag alongside the fixed provenance columns, for the long tail of environment facts nobody filters on; (4) checkpoint/progress SCRATCH — write-heavy, read-whole, shape varies by kind. Graduation rule: any JSONB field that starts being queried graduates to a real column or table via migration. What must NOT happen is today's failure shape: queryable state (blockers, reasons, history) buried inside a progress blob.

  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Design input from the dsh-superagent comparison (full standalone analysis: #3450; Drew directed posting the adoptable set here, 2026-11-01). Items for THIS issue's design:

    • Attention as a note type (their strongest idea): the question to a person is a structured field on the work item — question, options, recommendation, and the answer returns to the same item. Fits piece 4 directly: typed notes gain an attention type with those fields; lane escalations write the note and the chat line renders it; the morning readout lists open attention notes instead of re-deriving them.
    • Derived waiting-on stage: stage computed from facts by a precedence table (undelivered → waiting on person/answer → person/review → blocked/failed → queued → idle → working), and the label names WHOM it waits for. Proposed: a derived waiting-class over status + blocker links + park kind + last note type (inputs: CI / review / person / budget / external); stored status_reason stays the human gloss. Also absorbs design(queue): budget-aware claiming — parks carry a minimum required time budget, claim matches the claimer's remaining shift budget #3418's external-wait vs capacity-wait as two rows of the one table (§3.3 of analysis: dsh-superagent-prototype vs our fleet — findings and adoptable ideas #3450).
    • Reason codes: notes/events carry a reason_code from one controlled vocabulary (seed: ci_wait, codex_review, quota_reset, capacity, blocked_on_task, drew_decision, upstream_pr, …) beside the free-text body; terminal verbs validate the code. One vocabulary rendered for agents, humans, and views.
    • Mini-outbox for side effects: piece 4's "system mirrors notes back to GitHub" IS an outbox consumer — generalize it: state changes store their side effects in the same transaction; an effects table + drainer executes with per-kind backoff, surfacing a kind as a system problem only after 3 consecutive failures of one item. This mechanizes the claim-evaporation repairs the fleet monitor currently does by hand.
    • Generated working-set columns / partial indexes for the hot reads (claimable-now, parked-due, blocked-on-open-task), per their persistence design; pairs with the normal-form ruling.
    • Accept mode per producer (off / observe / auto) is schema-shaped (a mode column on producer/queue config) — the observe on-ramp for new producer rules; the behavioral half lives in the producer job bodies.
    • Few-concepts test: apply their rejected-concepts discipline to this design's own vocabulary before implementation packets split — a need becomes an attribute of an existing concept, or it justifies being new.
    • Authority strength: their Mandate records grant strength (explicit vs inferred) and revalidates the grant before resume. Ours revalidates the work's state but never the authority as a distinct check; piece 6 provenance should record strength (Drew-direct vs fleet-inferred) at creation.
  7. developing-today commented on Nov 1, 2026

    @developing-today
    Contributor
  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Correction to this issue's migrations premise (2026-11-01 ~02:52 CST): proper migrations DO exist. The muse-dev chain lives at ~/workspace/supabase/muse-dev/migrations/ — but the live tree held only 0015-0019; links 0001-0014 (including 0008_repo_queue_pilot, 0009_cancel_repo_task, 0012_claim_park_resume_attempts, 0014_revive_on_enqueue) survived only in a trashed 2026-10-28 snapshot. Those files have now been restored into the live migrations directory (25 files, 0001-0019, verified by listing), so piece 0 of this design changes from "establish a migration discipline" to: verify the restored chain against the live schema (hand-applied changes since 2026-10-28, if any, are still unrecorded) and land all six pieces as new migrations in the existing chain. The ad-hoc SQL observation stands only for post-chain drift, which the design should inventory.

  9. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Transferred from Slack #alignment at Drew's direction (2026-11-01). Aster (@drewrypope-aster) reviewed the dsh-superagent design package (#3450 / #3424 / #3418) in the thread on Muse's 03:29 CST pass; her replies are below verbatim (formatting converted from Slack mrkdwn to GitHub markdown; wording unchanged). The identical text is posted on all three issues so each record is self-contained.


    Aster's acknowledgment (03:29 CST):

    I’ll review the package against the original dsh docs, focusing on genuine weaknesses, the deliberate differences worth challenging, and possible misreadings. I’ll treat Drew’s sketches as design direction and keep this to review, with findings returned here.


    Aster's review (03:33 CST):

    Reviewed #3450, #3424 and #3418, including their comments, against dsh docs 000–008. My conclusion: the shared architecture is real, but “same path, mostly plumbing to borrow” understates their strongest advantage. Their docs specify ownership and recovery across transitions more precisely. Our proposed schema is richer in some areas, but records alone do not establish those guarantees.

    This is a documentary design review, not a runtime audit of either system. I would distinguish “implemented,” “proposed,” and “verified” rather than count “have (in design)” alongside shipped capabilities.

    Where I would correct the comparison

    • Merge authority: dsh does not categorically require humans to merge. Its Executor can enable auto-merge through another explicitly named account’s CLI. Human merge is the fallback. The real comparison is attribution and readiness safeguards: their defaults require an own-Ticket PR, approvals covering the head, fewer than 200 changed lines, and withdrawing auto-merge before a new revision. Keep delegated merge authority, but compare its safeguards rather than calling it an inversion. 003: Merging
    • Observe-first: they explicitly permit graduating to auto. Our mature-auto posture plus observation for new or materially changed producers is compatible. Their mode is Policy-level; per-producer modes are our adaptation. Explicit requests work in every mode, which matters when defining what “off” means. 002: Accept modes
    • Reserved capacity: their extra slot lets a human message/status request continue an existing Ticket and precedes new launches. It does not interrupt active work or give a newly queued expedited task a full shift. #3418’s proposal is a new scheduling policy, not direct adoption. Raising a bundle cap cannot give a worker with ten minutes left the 120 minutes a task needs. 003: Concurrency
    • Expiration: the 24-hour/latest/undismissed rule applies to unavailable Eval Decisions shown for attention, not all unanswered Attention questions. Do not age out Drew’s pending decision by importing it. 002: Unavailable Decisions
    • Storage and reconstruction: dsh describes aggregate JSON rows with generated working-set columns, not normal-form consensus. Retained Decisions/dispositions also do not establish full historical state reconstruction. #3424’s “resurrect any moment” is a stronger proposed contract with its own proof burden. 007: Persistence
    • Waiting and escalation: they reject Eval scheduling delayed Triggers, not all persisted timers in the core. Queue-native not_before is compatible with that separation. Likewise, one concrete question can include options, consequences and a recommendation. Their questions return through chat too; this is not “panel questions versus substantive chat.” 001: Rejected concepts, 004: Prompts
    Where our design needs stronger contracts

    1. Authorization, execution identity and relationship provenance are different things. Runtime/session/host/pid/IP explains who wrote. A grant reference explains who authorized what scope under which rule/version. A derivation basis explains why a link exists. None substitutes for the others. Add claim-generation/fencing semantics so an expired worker cannot write after its replacement claims the task. Context and inferred links must never silently expand authority.
    2. Durable input ownership and external-effect reconciliation come before more vocabulary. dsh specifies stable source-event IDs, cursor advancement after storage, follow-up ownership/hand-back, consumed-input accounting, and replacement Assignment recovery. Its cookbook also addresses remote success followed by local failure using outbound idempotency. An outbox must handle “GitHub accepted the comment; the drainer crashed before recording success.” Task mutation, event and effect intent should commit together; delivery needs a stable identity and reconciliation, not blind retry. Renewable task dedupe is not immutable event identity. 003: Restart, 008: Cookbook
    3. Attention needs a lifecycle, even if stored as a note type. Give each question identity, an answer binding, and open/resolved/superseded state. Deriving waiting-on from “last note type” is unsafe: a later finding must not hide an unanswered question. Keep all underlying blockers even if one precedence rule chooses the displayed stage. That is what makes a chat readout trustworthy without requiring another UI.
    4. Inferred blockers need satisfaction and retraction rules. An issue, its queue task and a PR claiming to close it are not interchangeable proof of completion. Cancelling the task or closing the PR unmerged must not unblock its dependent. Track the assertions supporting an inference so withdrawing one basis removes only its unsupported consequences, not an independently deliberate link. Pairwise links can support this; no general graph framework is required.
    5. Budget fit needs a starvation story. A maximum timebox is not necessarily the minimum uninterrupted budget. If a task requires 120 minutes but no lane can ever offer that, repeated skipping is not successful scheduling. Preserve the fit predicate under priority overrides, identify impossible fits, and specify whether human-priority work gets a fresh eligible worker, reserved start capacity or checkpointed yielding. Keep external-condition readiness separate from a recheck time: elapsed CI wait does not prove CI passed.
      Pushback on the deliberate differences

    Keep fresh shifts, phone-friendly chat/GitHub, and queue-native waits. But fresh context does not itself guarantee no compaction or no waiting; those require actual runtime bounds. Per-task workspace isolation also does not require a long-lived session.

    I would narrow “GitHub-canonical” to human discussion and code artifacts, then state authority per field. Queue leases, admission decisions, skipped inputs and pending effects are not automatically auditable from the repo because notes are mirrored. Define reconciliation and avoid two competing sources of truth.

    Do not copy dsh’s trust model wholesale: its workers run as the operator OS user, and the docs explicitly say wrappers/directories are not a security boundary. Its live Mandates also survive Policy changes, with notifications rather than automatic revocation. Those are tradeoffs to examine, not safety guarantees to inherit. 006: Isolation

    Five acceptance cases I would put ahead of implementation splitting

    • Cancel the task representing a blocking issue and close its resolving PR unmerged: the dependent remains blocked.
    • Open Q1, append a finding, supersede it with Q2, then receive Q1’s late/duplicate answer: Q2 remains correctly unanswered and work does not resume twice.
    • Present an expedited 120-minute task to a worker with ten minutes left: no cap override claims it; a fresh eligible worker can.
    • Crash after GitHub accepts an effect, then resume the old worker after lease reassignment: reconcile delivery and reject stale ownership writes.
    • Evaluate inferred work in auto, switch to observe before acceptance, then replay the event: no stale acceptance or duplicate execution.
    My recommendation is to retain the overall direction and revise the comparison before using it as design justification. Prioritize authority/ownership, atomic transitions and recovery, then structured attention and blocker semantics. The detailed link model and budget matching remain promising fleet-specific contributions; their value needs to be demonstrated by those failure cases. No implementation or issue changes made.

  10. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 2, 2026
  11. 127 remaining items

  12. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  13. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 4, 2026
  14. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  15. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 4, 2026
  16. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  17. added
    queue/queuedHas a queued task in the Supabase agent queue
    and removed
    work/assignedQueue worker lifecycle state
    on Nov 5, 2026
  18. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: fleet monitor
    • Session / ID: task 6637 (repo-fleet-monitor run 2026-11-05T15:39:47Z)
    • Found via: monitor reconciliation (Duty 1 claim/label divergence repair)
    • Task: Claim evaporated: task 6637 is queued and unclaimed in repo-health-code-pilot, but this issue still carried claimed labels. Labels reset to the truth (queue/queued + status/needs-triage). Checkpoint stage at evaporation: handed_off.
    • Expectation: The next lane claim picks task 6637 up from the queued state and posts a fresh pickup note.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

priority/highImportant and time-sensitivequeue/parkedQueue task is parked (waiting on CI, time, or an event)queue/queuedHas a queued task in the Supabase agent queuesource/creatorFiled by the new-issue-creator jobstatus/needs-triageNot yet triaged; needs a first lookstatus/parkedDeliberately paused with a recorded resume conditiontask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions