Skip to content

Instantly share code, notes, and snippets.

@Coder1400
Created September 15, 2026 01:16
Show Gist options
  • Select an option

  • Save Coder1400/a5f42fc320a10346b47bdd687fe9dceb to your computer and use it in GitHub Desktop.

Select an option

Save Coder1400/a5f42fc320a10346b47bdd687fe9dceb to your computer and use it in GitHub Desktop.
Workforce Phase 0 — Minimal Assignment and Attempt lifecycle

Parent and dependencies

  • Parent: #1 — Workforce Foundation (Phase 0)
  • Depends on: #6 — accountable Human/AI Agent foundation (completed)
  • Depends on: #14 — exact Attempt interaction history (completed)

In one sentence

Create one durable Assignment that exactly one AI Agent Attempt can claim, finish, or recover after a crash, while keeping the Event ledger authoritative and the current Assignment view synchronously up to date.

Why this matters

Workforce Events can already carry assignment_id and attempt_id, and the system can reconstruct model/tool history inside an Attempt. But no Assignment actually exists yet, and nothing prevents two Runners from starting the same work.

Before calling a real model or tool, Workforce needs a safe work boundary that can answer:

  • What task is being performed?
  • Which Agent and configuration claimed it?
  • Is an Attempt currently active?
  • Did the Attempt complete, fail, or disappear in a crash?
  • May another Attempt safely try the same Assignment?
  • Which Entity is the completed outcome?

This ticket builds that boundary. It does not run the Agent yet.

Plain-language behavior

An Assignment is one outcome-bearing task, similar to a project-board ticket. It remains until the task has an outcome.

An Attempt is one bounded try by one AI Agent to complete that Assignment. Failed and abandoned Attempts remain in history; another Attempt may try the same Assignment.

Example:

A Human creates Assignment A:
“Read the supplied document and return its access code.”

Assignment A is ready.

Runner 1 and Runner 2 both try to claim A.
Runner 1 wins and starts Attempt T1.
Runner 2 is rejected before doing any work.

Runner 1 crashes.
Its claim expires.
A System Actor records T1 as abandoned and returns A to ready.

Runner 2 starts Attempt T2.
T2 produces output Entity E and completes.
Assignment A is now completed and cannot be claimed again.

The claim expiry only detects an abandoned Runner. It is not an Agent token, money, tool-call, or time budget.

Approved Phase 0 decisions

  • Assignment means one committed task with a terminal outcome, not an Actor job description or standing responsibility.
  • One Assignment may have several sequential Attempts, but at most one active Attempt.
  • An Assignment begins unclaimed and ready.
  • The Runner explicitly selects the working AI Agent for Phase 0; there is no scheduler or autonomous work allocation.
  • A claim pins one active AI Agent and one existing configuration version.
  • Successful completion records one primary output Entity and makes the Assignment terminal.
  • A failed or abandoned Attempt returns the Assignment to ready without erasing the failed Attempt.
  • A claim has an explicit expiry so crashed work can be recovered. Phase 0 has no lease-renewal heartbeat or permanent reaper process.
  • Recovery is performed and attributed to a System Actor; it is not attributed to the crashed Agent.
  • Normal commands append the Event and update the Assignment Projection in the same PostgreSQL transaction. No continuously running projector is required.
  • The Event ledger remains authoritative. The Assignment Projection is rebuildable current state.
  • Assignment Projection freshness and recovery use the same behavior already implemented for Actors: a dedicated versioned cursor, relevant-Event stale checks, guarded lifecycle appends, synchronous Event/Projection/cursor updates, and maintenance rebuild.
  • Attempt-level budget placeholders were removed in PR #46. This ticket does not reintroduce budgeting under another name.

Required behavior

Create an Assignment

A trusted caller creates an Assignment with one exact goal/input Entity.

The command must atomically:

  1. validate organization and creator attribution;
  2. validate or create the referenced goal Entity in the same organization;
  3. append assignment.created;
  4. create the current Assignment row as ready.

An exact retry is a no-op. Reusing an Assignment or Event identity for different facts fails without changing state.

Claim an Assignment and start an Attempt

Given a known Assignment ID, selected AI Agent, configuration version, Attempt ID, and claim expiry, the command must atomically:

  1. lock and read the current Assignment;
  2. require it to be ready and incomplete;
  3. require the selected Actor to be an active AI Agent in the same organization;
  4. require the pinned configuration version to exist for that Agent;
  5. append attempt.started with the Assignment, Attempt, Agent, configuration, and claim-expiry facts;
  6. change the Assignment Projection to running with that active claim.

Two concurrent claims for the same Assignment cannot both commit. The losing caller must not append attempt.started or perform work.

End an Attempt

Only the currently active Attempt may end normally.

The command must append attempt.ended, clear the active claim, and update current Assignment state in the same transaction:

Attempt outcome Required Assignment result
completed completed, with exactly one primary generated outcome Entity
failed ready, with no completed outcome Entity

Completion and retry behavior must not rewrite prior Attempt Events.

A late result from an Attempt that has already been recovered or replaced must be rejected rather than completing the Assignment.

Recover an expired claim

Phase 0 provides an explicit recovery command; it does not run a background reaper.

Given an expired active claim, the command must atomically:

  1. lock and re-check the same active Attempt and expiry using database time;
  2. append attempt.ended with outcome abandoned, linked to its attempt.started Event;
  3. attribute the recovery Event to the System Actor performing it;
  4. clear the active claim and return the Assignment to ready.

If normal completion races recovery, row-level serialization must allow exactly one terminal result to commit. Recovery before expiry, recovery of a different Attempt, and repeated conflicting recovery must fail without changing state.

Read and rebuild current state

Provide organization-scoped reads for one Assignment and its current claim.

Generic Event append must reject assignment.*, attempt.started, and attempt.ended. These lifecycle Events must go through guarded Assignment commands so an Event cannot commit without its synchronous Projection update.

Before using current Assignment state, a command must lock the Assignment registry and inspect its versioned per-organization cursor. If any applicable Assignment or Attempt lifecycle Event exists after that cursor, the command must raise a typed AssignmentStateStale error and write nothing until maintenance rebuild catches up. It must not reconcile or guess inline.

The cursor need not equal the organization's overall Event head. Later llm.*, tool.*, Actor, or other unrelated Events do not make the Assignment Projection stale because they do not change Assignment lifecycle state.

Normal operation must not depend on a projector worker:

validate command
→ append Event
→ update Assignment Projection
→ commit both together

Maintenance rebuild must clear one organization's Assignment Projection and replay supported Assignment/Attempt Events in org_seq order to reproduce the same state, active claim, expiry, and outcome Entity identity without changing Event history.

Technical contract

The implementation should add only the smallest required surfaces:

  • a version-1 assignment.created payload and fixture;
  • the claim-expiry fact required on attempt.started;
  • a rebuildable assignments current-state table with organization ownership, state, goal Entity, active claim, outcome Entity, and last applied sequence;
  • a versioned per-organization Assignment Projection cursor and typed stale-state error matching the Actor registry's freshness behavior;
  • typed Python commands for create, claim/start, end, and expired-claim recovery;
  • guarded PostgreSQL command boundaries that append Events and materialize Assignment state atomically;
  • rejection of Assignment/Attempt lifecycle kinds at the generic append boundary;
  • organization-scoped Assignment reads;
  • bounded maintenance rebuild support.

Historical Attempts remain in the Event ledger and existing Attempt-history reader. Do not add a second historical Attempt store merely to duplicate those Events.

Application roles must not directly mutate Assignment Projection rows. Same-organization foreign keys and guarded command boundaries must preserve Entity, Actor, Assignment, and Attempt identity.

Declarative SQL under supabase/schemas/ is authoritative. Generate the migration from the settled declarative schema and verify reset/schema parity.

Required proof

Use real PostgreSQL transactions and deterministic coordination where concurrency matters:

  1. Create Assignment A with a real same-organization goal Entity and observe ready state.
  2. Race two claims for A and prove exactly one attempt.started Event and one active claim commit.
  3. End the winning Attempt as failed; prove A returns to ready and the failed Attempt remains in history.
  4. Start another Attempt, expire its claim, and recover it as a System Actor; prove it becomes abandoned and A returns to ready.
  5. Race normal completion against expired-claim recovery and prove exactly one result commits.
  6. Start a final Attempt, complete it with an exact output Entity, and observe A as terminal completed.
  7. Prove A cannot be claimed or completed again and a stale Runner cannot submit a late result.
  8. Rebuild the Assignment Projection from zero and prove the Assignment state, active-claim absence, output Entity, and Attempt history are unchanged.
  9. Privileged test setup inserts an unprojected Assignment/Attempt lifecycle Event after the Assignment cursor; prove a claim raises AssignmentStateStale and writes nothing until rebuild.
  10. Advance the organization head with only unrelated model/tool Events; prove the Assignment is still current and a valid command is not falsely rejected.
  11. Prove generic append cannot write Assignment/Attempt lifecycle Events and bypass synchronous materialization.
  12. Prove cross-organization Actor, Entity, Assignment, and Attempt references fail atomically.
  13. Prove exact retries are no-ops and conflicting identity reuse fails closed.

Done when

  • assignment.created has one active version-1 payload and fixture with no budgeting fields.
  • One exact goal/input Entity is required when creating an Assignment.
  • Assignment creation appends its Event and materializes ready state atomically.
  • Claiming pins one active AI Agent, configuration version, Attempt ID, and explicit claim expiry.
  • Concurrent claims cannot both commit or produce duplicate work.
  • completed requires one primary outcome Entity and makes the Assignment terminal.
  • failed and recovered abandoned Attempts return the Assignment to ready while preserving history.
  • Completion-versus-recovery and stale-Runner races are deterministic and cannot produce conflicting terminal results.
  • Current state is available immediately after command commit without a live projector worker.
  • Assignment freshness, stale-state rejection, generic-append protection, cursor advancement, dirty marking, and maintenance rebuild have behavioral parity with the Actor Projection.
  • Unprocessed Assignment/Attempt lifecycle Events block commands, while unrelated later Events do not create false staleness.
  • Rebuild from authoritative Events reproduces the same Assignment Projection.
  • Organization boundaries, attribution, idempotency, application permissions, and rollback are tested at the real database boundary.
  • No general scheduler, autonomous allocator, recurring-work model, budget subsystem, or second Attempt-history store is introduced.
  • Declarative schema, generated migration, reset database, focused tests, full checks, pre-commit, and schema parity pass.
  • Current technical documentation matches the implementation and deferred boundaries.

Out of scope

Real model calls · real tool execution · Agent loop · standing or recurring responsibilities · autonomous Assignment generation · priorities · dependencies between Assignments · scheduler · shared work pool · multi-Agent delegation · Human Attempts · waiting/handoffs · cancellation lifecycle · lease renewal · permanent reaper service · runtime token/cost/tool/turn budgets · external side-effect exactly-once guarantees · API/UI

Sources of truth

  • concept/model.md §5 — Assignments and Attempts
  • concept/decisions/0005-assignment-and-attempt.md
  • docs/architecture/proposals/assignments-and-runner.md
  • src/workforce/events/models.py
  • src/workforce/events/payloads.py
  • src/workforce/events/registry.py
  • src/workforce/interactions/history.py
  • supabase/schemas/01_events.sql
  • supabase/schemas/05_append.sql
  • AGENTS.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment