Agent Resource Quotas and Task Lifecycle Governance

Follows the complete user flow from configuration, creation, queueing, and execution to terminal-state diagnosis, describing current OpenProgram capabilities, resource limits in reference implementations, and the unified governance contract still required.

Document statusDesign and implementation partially complete; execution paths still have gaps
Product boundarySingle user, local machine, persistent worker
Acceptance statusPrimitives and some entry points implemented; complete acceptance pending
Implementation-status boundary.Job, ResourceGovernor, admission, resource projection, and time boundaries already exist. This does not mean all providers, runtime/idle paths, Web/TUI/CLI/model entry points, and recovery paths are uniformly integrated. The evidence below is limited to source and currently visible protocols; incomplete execution paths and user surfaces are listed separately.

00 Scope and final decisions

This design governs agent/Job eligibility, per-session queue and execution capacity, parent-child budgets, actual usage, termination, and recovery. Existing agent.max_spawn_depth, agent.max_spawn_fanout, agent.max_messages continue governing call topology and cycles, without reinterpretation as resource budgets.

DecisionConclusionReason
max_live_globalDo not addThe existing process-level ThreadPoolExecutor already uses OPENPROGRAM_JOB_WORKERS to control global execution capacity, default 4. Another equivalent setting would create conflicting global limits. The UI displays effective worker capacity read-only.
max_live_per_sessionAddPrevent one session from consuming every worker; count only tasks actually executing whose stop has not yet been confirmed.
max_queued_per_sessionAddLimit admitted but not running tasks to prevent an unbounded persistent queue. Queued tasks consume no live slot.
max_jobs_per_sessionAddLimit cumulative successful admissions over the session lifecycle; terminal tasks do not return this quota.
token / cost / runtime / idleInclude all, with different enforcement conditionsTokens and time are enforceable with safe upper bounds and cancellable boundaries; costs are enforceable in advance only when prices are known. Unknown cost must not be treated as zero.
Factual ledgerReuse usage ledgerActual provider usage remains recorded only in usage_events. Admissions and unsettled reservations are execution state, not a second actual-usage ledger.

Outside this designBackground-session UI, MCP server, purchases or top-ups, organization billing, and multi-machine cluster scheduling. These systems may later reuse shared DTOs, reason codes, budget scopes, and reservation protocols; their product interactions are not assumed here.

Collaboration counts and resource limits measure different operations

The Agent collaboration design §5 specifies collaboration operations, message counts, and spawn counts; this page specifies durable execution admission, capacity, and resource budgets. max_jobs_per_session is not another name for spawn depth or per-turn fan-out. A current Job's id is also its canonical execution_id; job_output(job_id=...) retains its parameter name and accepts that same identity.

LimitScope and counted operationBehavior at the limit
agent.max_spawn_depthGeneration count in the execution context; only agent(...) creating a new branch increments the child's generation. Dispatch onto an existing branch does not.Refuse this new branch; existing branches may still receive work within the other limits.
agent.max_messagesA path-local message count: spawn, agent(to=...), and send_message set the target count to sender plus one. Automatic follow-up reuses the completed Job's count without another increment; parallel calls in the same sender turn do not share a cumulative counter.Refuse new collaboration deliveries; do not cancel existing Jobs or stop other tools in the current turn.
agent.max_spawn_fanoutNew branches with creates_agent=true, counted by caller (session_id, turn_id); deliveries onto existing branches do not count.Refuse more branches in that turn; child completion does not return this per-turn count.
max_jobs_per_sessionCumulative successful admissions for the execution target session (Job parent_session_id), including new Jobs on new or existing branches. Cross-session caller provenance is recorded separately.Refuse new admission with quota.jobs_exhausted, retryable=false; admitted Job termination does not return the cumulative count.
max_queued_per_sessionTarget-session admissions in preparing or queued.Refuse new admission with quota.queue_full, retryable=true; rejection creates no new Job.
max_live_per_session / scheduler_capacitySession / global claims in live or stopping; work not yet confirmed stopped still occupies capacity.Keep admitted Jobs queued until a live claim is available; capacity waiting is not execution failure.

For the three collaboration limits, 0 disables that limit check; message and generation values may still propagate through execution contexts. Resource limits use a different schema: null means unset/inherit, every non-null value must be positive, and 0 is invalid. Queued/cumulative/token/cost/runtime/idle limits remaining unset after inheritance are disabled; the effective live default is still scheduler capacity.

agent(to=...) creates no branch but creates a new execution, so it checks the message limit and requests target-session admission. send_message also creates no branch; its current idle-target path calls run_agent_turn_async to create a creates_agent=false Job, consuming target-session cumulative admission. Ordinary busy-target inbox receipt precedes creation of that execution: queued delivery does not mean Job admission succeeded, and a later refusal must be reported to the sender. Messaging is not exempt from resource limits.

Target contract for observation and cancellation.The message limit constrains new collaboration deliveries only; result retrieval, resource observation, and cancellation of existing executions remain available after ownership and permission checks. Current job_output still has can_use=delegation_budget_left and is hidden when the message budget is exhausted; this interface behavior remains to be corrected. The registered agent tools are agent, list_jobs, and job_output; there is no standalone job_stop tool. Cancellation reuses canonical execution control and the internal JobRunner.cancel_execution method; an internal method name is not a callable model tool.

01 Current implementation: admission, resource accounting, and execution control integrated, with explicit gaps

The current worktree integrates Job, ResourceGovernor, durable admission, owner lease/reconcile, execution-time boundaries, and resource views into major execution paths. Source confirms these capabilities. Product tests were not rerun in this round; historical results remain in Git. Below, source-confirmed implementations, source-confirmed gaps, and unverified scope are distinguished.

Existing mechanismConfirmed behaviorGovernance gapEvidence
Global execution pool and admissionOPENPROGRAM_JOB_WORKERS remains the sole global capacity; runner and synchronous agent entry points call ResourceGovernor.admit_job(), and SQLite protects counts with BEGIN IMMEDIATE.JobRunner starts a durable dispatcher, reconciler, and budget loop; the whole-product gate was not rerun in this round.openprogram/agent/job/runner/ · admit_job_entity / spawn_job / _run_one
openprogram/agent/resource_governance/ · admit_job / try_start
Persistent task and resource stateJob already has budget_scope_id, effective_limits, reason_code; the usage DB has admissions, budget scopes, and reservations.Job, canonical execution store, ResourceSaga, and startup recovery source exist; the crash/concurrency matrix was not reverified in this round.openprogram/agent/job/types.py
openprogram/usage/ledger.py
openprogram/agent/resource_governance/
Session limitsmax_live_per_session, max_queued_per_session, max_jobs_per_session are in the config schema and checked atomically by the governor.Current source includes JobResourceView and Web/CLI/TUI/model surfaces; entry-point consistency and fairness regressions were not rerun.openprogram/config_schema.py
openprogram/agent/resource_governance/ · resolve_resource_limits / _capacity
Cancellation and recoveryCancellation continues cascading through parent-child relationships; running tasks use cancel/kill/watchdog, and the governor has lease, stopping, and release primitives.Current source includes RuntimeControlService, terminal recovery, lease reconciliation, and stopping/release paths; cross-process concurrency evidence was not reverified.openprogram/agent/job/runner/ · cancel_execution / _run_one
openprogram/agent/resource_governance/ · renew_lease / request_stop / release_task
token / costBudgetedRequest connects the shared provider stream entry points to reserve_provider_request before credentials/network access; output caps are clamped and terminal usage settles the reservation. The usage ledger retains unknown-cost provenance.The production path uses reserve_provider_request rather than the standalone reserve_tokens/reserve_cost methods. Accounting checks and unknown-price refusal under a cost ceiling exist in source; provider coverage and failure-injection gates were not rerun in this review.openprogram/providers/stream.py · stream_simple / stream
openprogram/providers/budget.py · BudgetedRequest
openprogram/agent/resource_governance/ · reserve_provider_request
runtime / idleConfiguration and budget scopes can store max_runtime_seconds, idle_timeout_seconds; existing provider/tool timeouts remain independent.The current runner includes a budget loop, time limits, bounded_operation_timeout, and a non-preemptible guard; per-operation regressions were not reverified.openprogram/config_schema.py
openprogram/providers/utils/deadline.py
openprogram/agent/job/runner/
User stateWeb and runtime control provide execution observation/cancellation and state broadcasts; model tools expose list_jobs/job_output; JobResourceView can generate the basic structure.Current source includes JobResourceView, job_status resource projection, and Web/CLI/TUI/model read surfaces; cross-interface consistency tests were not rerun.openprogram/agent/resource_governance/ · JobResourceView / build_task_resource_view
apps/server/openprogram_server/_webui/ws_actions/job.py
openprogram/programs/tools/agents/agent/list_jobs/
Creation-entry audit.The initial batch connected runner and synchronous agents to the governor, but the complete gate still requires matching admission evidence for WebSocket, busy-target inbox, internal retries, and later entry points, verifying that rejected Job creation leaves no new Job, branch, Job-bound inbox entry, or execution-accepted broadcast. Ordinary message receipts follow the separate rule in §05.

02 Other projects: compare counting units, rejection timing, persistence, cancellation, and visibility

Reference values explain counting units and execution models; they do not determine OpenProgram defaults. Full collaboration evidence is in Agent collaboration implementation comparison, with existing internal designs in Agent collaboration designandAsync Job lifecycle.

ProjectActual counting units and limitsRejection/blocking pointPersistence and cancellationUser visibilityRelevance to this design
Claude CodeBinary snapshot: spawn depth 3, concurrency 20, inbox 50; matrix evidence separately records per-session cumulative spawn counts.Depth/concurrency checks occur during creation; fork paths differ on retaining tools but rejecting at call time.Traceable agentId; cascading cancellation confirmed. The binary and leaked source tree are different implementations.list/output/stop and agent state.Live and cumulative counts must be separate, and evidence versions explicit; do not adopt its values.
Codex CLIGlobal concurrency 6; V2 per-session 4; V1 depth 1; V2 calculates depth without enforcing it.The execution controller checks concurrency before starting.ThreadId; list/wait/interrupt; complete V2 interrupt cascading is unconfirmed.The model can query and interrupt.Supports separating per-session live limits from global scheduler capacity; depth and concurrency cannot substitute for each other.
OpenClawDefault depth 1, 5 live children per parent, concurrency pools 8/4, plus message-cycle limits.Spawn rechecks depth, children, and concurrency.runId/session key; host-maintained run; child cancellation cascading confirmed.list and host state.Adopt submission-time rechecks and cascading, not overlapping children/global settings.
OpenCodePermission shape implies depth 1; no numeric concurrency limit confirmed.Tool permissions constrain child-session creation/calls.Child session id and background metadata; host cascading cancellation.Background-task state is visible, but model queries are limited.Permission constraints are not resource quotas; state should be exposed independently.
HermesDefault depth 1 (configurable to 3), child concurrency 3, 50 iterations per child, caller timeout 600 seconds, and heartbeat.Limited at thread-pool submission and caller wait.Synchronous wait; no stable model-facing job id; host can cascade cancellation.Visible to dashboard/host; limited model control surface.Adopt runtime/idle signals; caller wait timeout is not proof of execution termination.
CodeBuddyOfficial local-package docs specify at most 16 concurrent workflow agents and 1000 agents per run.Workflow orchestrator enforces phase/run limits.Resumable in the same session; agent-team docs also state teams are not restored by /resume.Workflow UI shows phase agent counts, tokens, elapsed time, and abort.Separate concurrency, cumulative counts, and visible usage; do not adopt 16/1000 or extend resume claims to teams.
pi-monoExample extension: batch 8, concurrency 4, 50KB output per task.The extension limits batch/concurrency before spawning subprocesses.No durable job id/query; single-level process cancellation may leave deeper descendants.Primarily call results.Extension-local semaphores cannot cover persistent tasks and cascading recovery.

Adopt

  • Separate global scheduler capacity and per-session live limits.
  • Count live, queued, and cumulative tasks separately.
  • Stable job ids, cascading cancellation, and user-queryable state.
  • Show tokens, elapsed time, and reason together in task views.

Adapt or reject

  • Reject direct adoption of competitor values.
  • Reject depth, fan-out, or tool permissions as substitutes for resource admission.
  • Reject process-local semaphores alone; admission must be durable and atomic across processes.
  • Do not describe caller timeout or a terminal marker as execution termination.

03 Planned user flow: establish one governance boundary

01Inspect and configureGlobal defaults, session overrides, effective values
02Request creationUnified admission, atomic reservation
03Queue and executeSeparate queued and live
04Parent-child budgetsTighten only, shared remaining pool
05Meter and stoptoken, cost, runtime, idle
06Terminal releaseReturn slots, retain cumulative counts
07Restart recoveryIntent, lease, cross-process concurrency
08User diagnosisOne DTO for Web/TUI/CLI

04 Inspect and configure global defaults and session overrides

Current behavior
The config schema includes collaboration limits and agent.resource_limits; the resolver handles global/session/parent/job sources, and environment configuration controls worker capacity.
User impact
Configuration presence does not establish verified enforcement at every entry point or UI. Show configured values, sources, and effective values without presenting incomplete enforcement as complete.
Gap
Multiple equivalent global, parent, and child settings make precedence difficult to explain; using 0 for both unlimited and disabled perpetuates ambiguity.
Target behavior
One minimal agent.resource_limits object holds global defaults; session metadata overrides matching fields. APIs always return source, configured, and effective values. Tasks may only tighten effective session limits.
agent.resource_limits:
  max_live_per_session: integer | null
  max_queued_per_session: integer | null
  max_jobs_per_session: integer | null
  max_total_tokens: integer | null
  max_cost_usd: decimal-string | null
  max_runtime_seconds: integer | null
  idle_timeout_seconds: integer | null

All non-null values must exceed 0; null means unset/inherit. For compatibility, queued, cumulative, and the four budget types are disabled by default.max_live_per_session defaults to current scheduler capacity when unset; otherwise its effective value is the lesser of configuration and scheduler capacity.OPENPROGRAM_JOB_WORKERS remains the sole source of global execution capacity, exposed through the read-only scheduler_capacity field.

Only the owner may change global defaults and session overrides. Agent/task requests may supply only stricter token/cost/runtime/idle budgets, without changing live, queued, cumulative limits or increasing any owner limit. Extend existing config get/set with these fields and reuse session metadata; do not create a second quota configuration service.

max_total_tokens and max_cost_usd count governed agent/Job invocations carrying job_id within the session; ordinary foreground main conversations are excluded.max_runtime_seconds and idle_timeout_seconds is a time ceiling for each Job and its ancestor scopes. Changes to live/queued/cumulative limits do not terminate existing live/queued work, but immediately constrain new admission or queued → live; token/cost changes apply the new session ceiling at the next reservation. Runtime/idle defaults affect only subsequently admitted Jobs, avoiding retroactive deadlines.

05 Admission and atomic reservation when an agent creates a task

Current behavior
Synchronous/asynchronous agent tools and busy-target dispatch call runner/governor admission. The governor checks depth, fan-out, queue, and cumulative counts transactionally; the model tool retains a process-local fan-out precheck. Ordinary busy-target messages enter the inbox before Job creation on delivery.
User impact
Identical requests can differ by entry point; concurrent frontends/processes may pass non-atomic checks simultaneously; failed creation may leave Job, branch, inbox, or count residue.
Gap
Unified admission, cross-process transactions, and idempotent identity exist in source. Rejection without Job-creation side effects and notification of admission refusal after ordinary message queueing still require entry-point verification.
Target behavior
Every execution-task creation path must call ResourceGovernor.admit_job. Perform pure validation first, atomically check depth/fan-out and resource limits and reserve in SQLite, then create the Job, any new branch, and Job-bound inbox entry and publish execution-accepted events only on success. Rejection creates no Job. An independently authorized ordinary message receipt may precede admission.
Receipt versus execution admission.An ordinary busy-target send_message may persist a message receipt before a Job exists, subject to message authorization and inbox limits. This is distinct from a Job-bound inbox entry created by agent(to=...). If admission later fails, mark that receipt failed and notify its sender with the admission reason; do not claim an execution was accepted. The transaction below governs Job creation, not the earlier receipt.

Unified coverage

Durable intent across SQLite and jobs.json

  1. Generate job_id, admission_id and request fingerprint; validate arguments, target, and permissions without mutation.
  2. In the usage DB’s first BEGIN IMMEDIATE transaction, check depth, per-turn fan-out, and session/parent remaining capacity, insert a preparing admission, reserve one queue slot plus provisional fan-out and cumulative increments, then commit.preparing must be durable before jobs.json writes; every concurrent admission check includes its provisional occupancy to prevent cross-process overallocation.
  3. Use openprogram._compat.flock to protect per-session task-file read-modify-write and persist the Job. Do not broadcast or create other irreversible side effects yet.
  4. In a second BEGIN IMMEDIATE transaction, change admission from preparing to queued, finalize provisional fan-out/cumulative counts, and commit; only then create the branch and Job-bound inbox entry and broadcast execution-accepted status. An existing ordinary message receipt is linked to the admitted Job at delivery.
  5. Any failure triggers idempotent compensation. On restart, a durable preparing with a matching Job is finalized in the second transaction; without one, release queue and provisional counts transactionally. SQLite transactions must not span jobs.json file I/O.
Rejections use the stable structure: {accepted:false, job_id:null, reason_code, retryable, effective_limits, usage, capacity}. A full queue is usually retryable; exhausted cumulative or parent budgets are not, unless the owner raises limits or creates a new session.

06 Queueing and live concurrency

Current behavior
The runner uses durable admissions and a dispatcher. The governor checks session/global live capacity before claiming queued work; queued work has no live claim.
User impact
Queue/cumulative configuration and live claims exist, but complete cross-entry enforcement, fairness and queue diagnostics still require verified evidence.
Gap
A durable dispatcher and atomic resource claims exist in source; fairness, worker-loss recovery and cross-process admission/release require the full acceptance gate.
Target behavior
Admission first enters a durable queue. The dispatcher submits only when global worker and session live capacity are available, releasing queue and acquiring live in one transaction. Queued work consumes no live slot.
preparing→ queued→ atomic exchange live→ cancel/timeout stopping→ confirmed exit released

Compatible Job states remain pending/queued/running/completed/cancelled/errored; resource state persists separately.stopping is especially important: a Job may display cancelled, but its live lease cannot return until worker/thread/runtime termination is confirmed. This prevents overallocation when the 30-second watchdog only changes status.

The dispatcher selects the earliest globally ordered admitted_seq task whose session currently has live capacity, skipping temporarily ineligible sessions so their head task cannot block others. The global worker pool still determines total execution capacity.

Only the single execution process holding the existing worker lock may call queued → live and submit to the executor. Web/TUI/CLI/API processes only admit and durably enqueue, preserving the single-worker topology and preventing separate global thread pools per process. Multiple execution workers would require a separate scheduling design.

07 Parent-child budget allocation and tightening-only inheritance

Current behavior
Jobs carry budget_scope_id and effective_limits. The governor creates parent-linked scopes, resolves ancestor limits, and checks ancestor balances transactionally during provider reservations; §01 records provider integration status.
User impact
The scope model supports shared parent ceilings rather than independent child allowances. Users need the effective ceiling and shared_remaining to distinguish a local limit from currently available shared capacity.
Gap
The primitives and production reservation path exist; explicit-child allocation, sibling contention, and all entry-point propagation still require the full acceptance matrix.
Target behavior
A task request may carry one limits object containing only token/cost/runtime/idle budgets. Explicit child limits cannot exceed effective session limits, parent-task limits, or current parent-scope remaining capacity.

Deterministic rule without explicit child allocation

Do not divide the parent budget equally in advance or predict future work from fan-out. Children join the shared parent budget scope. Every provider/tool reservation atomically checks the task’s own ceiling, every ancestor scope, and session scope; the first successful durable reservation acquires capacity. This avoids idle static shares and does not misrepresent current remaining capacity as a guaranteed child allotment.

The UI labels shared_remaining as a shared live balance, not an allotment. Explicit child budgets create stricter local ceilings without permanently deducting from the parent in advance; actual reservations consume parent balance.

08 Metering and enforced stopping for tokens, cost, wall-clock, and idle

Current behavior
Governed provider calls reserve through BudgetedRequest before credentials or network access, clamp output, and settle terminal usage. The runner has a budget loop and bounded-operation checks; ungoverned legacy paths retain best-effort usage recording.
User impact
Protection depends on Job attribution and an audited provider/operation boundary. Unknown cost must remain distinguishable from zero, and a timeout result alone does not prove a synchronous thread stopped.
Gap
Reservation, attribution, unknown-cost handling, activity tracking, and operation boundaries exist in source. Adapter coverage, late settlement, and operation-specific stop confirmation require runtime acceptance; those gates were not rerun here.
Target behavior
The existing usage ledger alone records actual usage; add reservation state in the same DB. Atomically reserve before model/tool calls, settle using final provider usage afterward. Active timers and boundary checks jointly trigger existing cascading cancellation.
BudgetUnit and factual sourcePre-call enforcementDuring/after the callWhen reliable enforcement is impossible
tokenProvider-authoritative input + output; cache reads/writes are separate diagnostics, not added twice.Reserve input_upper_bound + max_output_tokens, and reduce the provider output cap to remaining capacity.Settle with final usage events and release the difference; concurrent requests include open reservations.Under a strict token budget, reject a new model request if no tokenizer or safe upper bound is available: quota.accounting_unavailable.
costKnown provider/model/rate and actual tokens; retain cost_source.Reserve a micro-USD upper bound only when all required prices are known.Settle using actual tokens/rates; aggregation also returns cost_known and unknown_cost_events.With a cost budget, unknown current-call pricing or existing unknown-cost events in the scope reject new LLM calls: quota.cost_unavailable. Without a cost budget, execution is allowed but cost displays as unknown, never $0; do not rewrite historical actual costs using later catalog prices.
runtimeMonotonic wall-clock from entering resource state live; queue wait is excluded.Each provider/tool timeout is the minimum of the operation limit and task time remaining.Timer expiry sets cancellation and cascades through existing parent-child relationships.Tools unable to exit reliably within remaining time cannot enter strict-budget tasks; return error.nonpreemptible_operation.
idleMonotonic time since the last meaningful activity.Record the call-start boundary; while waiting for children, link their activity.Provider data, tool progress, and child progress/terminal events update activity; transport keepalives do not.A tool with neither progress nor bounded timeout is non-preemptible.

Explicit bound for non-preemptible operations

Enforce exhaustion at model-request/tool-call boundaries, with active timers triggering cancellation promptly. Do not describe an executing operation as instantly terminated. Its maximum non-preemptible interval must equal the minimum of declared operation timeout, remaining task runtime, and system cancel grace. Synchronous in-process operations that cannot meet this must move to terminable subprocesses or be rejected for strict-budget tasks; asyncio.wait_for returning does not mean the executor thread has ended.

Accounting delay and crashes

09 Slot release and cumulative semantics after cancellation, failure, timeout, and completion

Current behavior
Job terminal state and resource admission/lease state are separate. The governor retains cumulative admissions and supports stopping and confirmed release; runner completion and recovery use these primitives.
User impact
A cancelled Job can still occupy live capacity while its execution is stopping. The UI must distinguish requested cancellation from confirmed exit and quota release.
Gap
The state separation and cumulative-retention rules are defined in code. Watchdog, late completion, and crash interleavings still need full-matrix verification; absence of this review evidence is not absence of the primitives.
Target behavior
Release queue/live only when execution termination is confirmed or lease-recovery rules hold. Increase cumulative count once on successful admission and never return it for any terminal state.
EventQueue slotLive slotCumulative tasksTerminal Job state
Admission rejectedNot occupiedNot occupiedNo incrementNo Job created
Preparing rollbackReleaseNot occupiedNo incrementNo Job or restored consistency
User cancels after queuedRelease immediatelyNot occupiedRetaincancelled
Cancellation/budget exhaustion after runningAlready releasedEnter stopping; release after confirmed exitRetaincancelled
Execution error/completionAlready releasedRelease after worker finally confirmsRetainerrored/completed
Retry identical job_idNo duplicate occupancyNo duplicate occupancyNo duplicate incrementReturn original task/conflict reason

Cumulative counts last for the session lifecycle. There is no implicit reset, and session archiving does not return quota. Users can create a new session or have the owner explicitly raise its override. Lowering limits cannot delete existing tasks; usage above the new limit only rejects later admissions.

10 Restart recovery, cross-process concurrency, and diagnosis

Current behavior
Job storage uses cross-process flock. Runner startup and its reconcile loop use durable admissions, owner leases, and ResourceSaga recovery; legacy unlinked Jobs follow the existing orphan policy. The worker lock keeps execution on one worker.
User impact
Admission and recovery span SQLite, Job files, and runtime ownership. Diagnosis must distinguish restored queued work, worker loss, and released resources while retaining the original execution identity.
Gap
Owner/lease, preparing reconciliation, file locking, and structured reason primitives exist. Process-death timing, simultaneous frontend writes, and repeated recovery still require the acceptance matrix; this review did not rerun it.
Target behavior
Single-worker execution remains supported; multiple UI/API processes compete through SQLite admission transactions. Each live admission carries owner/lease; reconciliation uses intent, Job, and lease facts to prevent permanent quota leaks.

Recovery rules

This design adds no cluster consensus or remote scheduler. If multiple workers are later allowed, the same job_admissions lease/owner contract may extend, but leader election, clock assumptions, and external-runtime fencing require a separate design.

11 User-visible state and reason codes in Web, TUI, and CLI

Current behavior
JobResourceView and stable resource reasons exist, with Web subscriptions, terminal/CLI adapters, and list_jobs/job_output projections. Cancellation uses canonical execution control.
User impact
Users need the same queue, cumulative, cost-known, and stopping explanations across surfaces. Data projection alone does not establish that every field is correctly displayed in every state.
Gap
The shared DTO exists; cross-interface field/reason parity, legacy records, unknown-cost display, and recovery states still require runtime and visual acceptance. No standalone Tasks panel is claimed.
Target behavior
Web, TUI, CLI, and model tools consume the same JobResourceView; wording may vary by interface, but fields and reason codes must match.
ViewRequired displayInteractionCompatibility
WebJob status + resource state, queue position, session live/queued/cumulative, four remaining budgets, known/unknown cost, and reason.Reuse existing task/branch cards and cancel; do not add background-session UI.Existing job_status events gain optional resource fields.
TUICompact session capacity, selected-task remaining budgets, and stop reason.Reuse task list/details/stop; no new full-screen product surface required.Missing fields render as legacy tasks, not guessed zeros.
CLIHuman-readable tables and stable JSON: effective limits, usage, reservations, reason, retryable.Reuse task-query and cancellation commands; JSON supports automated diagnosis.New fields remain backward-compatible; the Job state enum stays unchanged.
Model toolslist_jobs, job_output, and canonical execution control return the same reason and simplified remaining budgets; JobRunner.cancel_execution is an internal method.Admission rejection is a structured tool result, without fabricating a Job record.Keep original text results and add machine-readable fields.

Stable reason code

Phasereason_codeMeaning
admissionquota.queue_fullThe session’s durable queue is full; retry after capacity is released.
admissionquota.jobs_exhaustedSession cumulative task count reached its limit.
admissionquota.parent_budget_exhaustedParent-scope remaining capacity cannot accept the child limit.
admissionquota.token_exhausted / quota.cost_exhaustedInsufficient reservation capacity before the request.
admissionquota.cost_unavailableA cost budget is set, but provider/model pricing is unknown.
admissionquota.invalid_limitsInvalid value or child limit looser than parent/session limit.
admissionquota.accounting_unavailable / quota.admission_conflictAccounting is unsafe, or an idempotency key conflicts with existing request content.
terminalcancel.user / cancel.parent / cancel.sessionExplicit cancellation source.
terminalbudget.token_exhausted / budget.cost_exhausted / budget.runtime_exhausted / budget.idle_exhaustedA running task reached a budget boundary; Job becomes cancelled and cancellation cascades to descendants.
terminalerror.worker_lost / error.accounting_unavailable / error.nonpreemptible_operationExecution safeguards failed; Job becomes errored.
terminalcompletedNormal completion.

12 Stable schema and state contract

Actual usage retains usage_events as its sole source of truth. Admission/reservation tables below represent only occupied resources and unsettled exposure, never fabricated actual provider tokens/cost.

job_admissions(
  admission_id PRIMARY KEY,
  job_id UNIQUE NOT NULL,
  session_id NOT NULL,
  parent_job_id,
  caller_turn_id,
  creates_agent BOOLEAN NOT NULL,
  request_fingerprint NOT NULL,
  budget_scope_id NOT NULL,
  state CHECK state IN ('preparing','queued','live','stopping','released'),
  admitted_seq NOT NULL,
  owner_instance_id,
  lease_expires_at,
  created_at, started_at, last_activity_at, released_at,
  reason_code
)

budget_scopes(
  budget_scope_id PRIMARY KEY,
  scope_kind CHECK scope_kind IN ('session','task'),
  session_id NOT NULL,
  job_id UNIQUE,
  parent_scope_id,
  max_total_tokens,
  max_cost_microusd,
  max_runtime_seconds,
  idle_timeout_seconds,
  created_at
)

usage_reservations(
  reservation_id PRIMARY KEY,
  job_id NOT NULL,
  budget_scope_id NOT NULL,
  kind CHECK kind IN ('token','cost'),
  state CHECK state IN ('reserved','started','settled','released'),
  reserved_tokens,
  reserved_cost_microusd,
  request_started_at,
  settled_event_id,
  expires_at
)

usage_events + optional columns:
  job_id, budget_scope_id, reservation_id

Job JSON + optional fields:
  admission_id, budget_scope_id, effective_limits, reason_code

effective_limits is an audit snapshot at task start; configuration queries separately return each field’s configured, effective and source. Actual shared availability is computed live from the ledger and open reservations; shared_remaining explicitly denotes current ancestor/session shared-pool balances, not guaranteed Job allotments. Job JSON does not copy cumulative actual usage. Configure money as decimal fixed-point strings; store and compare integer micro-USD to avoid floating-point boundary differences.

JobResourceView

{
  "job_id": "...",
  "status": "running",
  "resource_state": "live",
  "reason_code": null,
  "retryable": false,
  "capacity": {
    "scheduler_capacity": 4,
    "session_live": {"used": 2, "limit": 3},
    "session_queued": {"used": 1, "limit": 8},
    "session_jobs": {"used": 17, "limit": 100},
    "queue_position": null
  },
  "budget": {
    "scope": "task_with_shared_ancestors",
    "tokens": {"actual": 12000, "reserved": 4000, "limit": 50000},
    "cost_usd": {"actual": "0.38", "reserved": "0.12", "limit": "2.00",
                 "known": true, "unknown_events": 0},
    "runtime_seconds": {"used": 91, "limit": 600},
    "idle_seconds": {"used": 4, "limit": 120},
    "shared_remaining": {"tokens": 34000, "cost_usd": "1.50"}
  }
}

13 Compatibility, migration, and implementation batches

Compatibility principles

BatchImplementation scopeEnablement conditionsSource status; acceptance not rerun
1 Accounting foundationSchema migration, task/budget attribution, unknown-cost aggregation, read-only effective limits/diagnostics.Old-database upgrade/rollback, unknown-cost, and existing usage-query compatibility tests pass.Migration and DTO source exist; compatibility gates not rerun
2 Admission and queueUnified creation entries, durable intent, per-session live/queued/cumulative, dispatcher, cross-process file lock.All entries covered; no overallocation across processes; every terminal state and crash point recoverable.Admission, dispatcher, and recovery source exist; complete entry-point and concurrency gates not rerun
3 Tokens and costShared parent-child scopes, reserve/settle, provider output cap, unknown-price fail-closed.Concurrent requests, late usage, settlement failure, and price-change tests pass.Shared provider call path is connected; coverage and fault-injection gates not rerun
4 Runtime and idleActive timer, activity contract, deadline clamp, bounded-operation contract, cascading stop.Model, tool, synchronous thread/subprocess, and stopping-lease tests pass.Runtime/idle loops and bounded-operation checks exist; operation-specific gates not rerun
5 User surfaces and gateOne DTO across Web/TUI/CLI/model tools, reason wording, compatibility docs, and mechanical checks.Cross-interface contract tests and all gates on this page pass.Shared resource DTO and surface adapters exist; cross-interface gates not rerun

14 Test matrix

Test areaMinimum casesMeaning of failure
ConfigurationGlobal defaults, session overrides, parent/task tightening only; null/positive integer/decimal validation; read-only worker environment display.Unpredictable effective values or overlapping global limits.
Collaboration/resource interactionDepth exhausted still permits existing-target dispatch within message/admission limits; messages exhausted denies new delivery while authorized observation/cancel remains; send_message-created Jobs count in target-session admission; topology 0 and resource null remain distinct.Resource limits can be bypassed or existing work cannot be observed/stopped.
Creation entriesSync/async agents, target inbox, Web/API, runner internals, and future registered entries; rejected Job creation leaves no new Job, branch, or Job-bound inbox entry; an earlier ordinary message receipt becomes failed and notifies its sender if later admission fails.A quota bypass exists.
Atomic admission20+ concurrent threads/processes; unique job ids; limit boundaries; fault injection before/after preparing.Overallocation, duplicate cumulative counts, or permanent leaks.
Queue/liveQueue consumes no live slot; atomic queued→live exchange; oldest eligible across sessions; full queue; stopping retains live.Session starvation, premature release, or unbounded executor queue.
Cumulative countNo return for completed/cancelled/errored; preparing rollback does not count; same-job-id retry does not duplicate; lowering limits.Terminal-state-dependent cumulative semantics or retry bypass.
Parent-child budgetsReject explicit child limits above parent remaining capacity; shared scope without explicit budget; concurrent sibling reservations; multi-level ancestor checks.Children can expand budgets or overspend concurrently.
Token/costSafe token bounds, provider output cap, no duplicate cache tokens; known/unknown prices; late/missing usage; settlement DB failure.Budgets are only observable afterward, or unknown costs become zero.
Runtime/idleExclude queue wait; provider data/tool progress/child activity count; keepalive does not reset; deadline clamp; reject non-preemptible tools.Time budgets are unenforceable or incorrectly terminate active tasks.
Cancellation and recoveryUser/parent/session/budget cascading; thread survives watchdog; worker crash at each transaction point; lease expiry; idempotent reconciliation.Premature release, surviving descendants, or permanent occupancy after restart.
User visibilityWeb/TUI/CLI/model DTO contract; queue position, shared remaining, unknown cost, reason/retryable; legacy task.Interfaces disagree or display unknown as zero.

15 Feature-matrix filled-circle mechanical gate

The feature-matrix row “concurrency and per-session cumulative spawn limits” at the foundational baseline current worktree baseline has not satisfied the following mechanical gates. Whether the current worktree satisfies them requires item-by-item source and verification evidence, not candidate-branch records:

  1. Stable configuration schema implemented; global defaults, session overrides, and effective values visible through Web/TUI/CLI.
  2. All agent/Job creation entries use unified admission, with no Web, synchronous-agent, target-inbox, or direct-runner bypass.
  3. Per-session live and cumulative admission are atomic across competing processes without overallocation or double counting.
  4. Queue/live occupancy, cumulative return, and stopping/live retention follow this page’s semantics.
  5. Completion, failure, cancellation, timeout, worker crash, and mid-admission crash release eligible queue/live capacity without permanent leaks.
  6. Web/TUI/CLI show effective limits, usage, queue/live/cumulative, and stable reason codes.
  7. Compatibility migration, concurrency/crash tests, documentation links, and feature-matrix scoring checks all pass.
Scoring boundary.This row accepts only concurrency and per-session cumulative spawn limits; completing all token/cost/runtime/idle budgets is not required to change it. If those become separate capability rows, each needs its own complete gate. Conversely, UI, schema, or a single-process semaphore alone cannot justify a filled circle.

16 Current implementation and verification boundaries

This section records implementations directly locatable in the current worktree and remaining acceptance boundaries. It excludes dates, commit SHAs, and migration history; consult Git for evolution.

ScopeCurrent source evidenceCurrent statusGaps requiring confirmation
Job entity and stateopenprogram/agent/job/types.py: Job, JobStatus, legal transitions, and worktree_id; openprogram/agent/job/store.py: persistence and state updates.ImplementedCrash recovery, terminal idempotence, and cross-process concurrency still require complete-matrix acceptance.
Runner and admissionopenprogram/agent/job/runner/: JobRunner, durable dispatcher, reconciler, budget loop, spawn_job, cancel_execution, list_jobs, await_job; ResourceGovernor is used during runner initialization and execution.Implemented and integrated into major execution pathsWhole-product gate, cross-process concurrency, and worker-loss evidence were not rerun in this round.
Job WebSocket and model toolsapps/server/openprogram_server/_webui/ws_actions/job.py: spawn/list/get; openprogram/programs/tools/agents/agent/list_jobs/, job_output/; runner provides cancellation.Major entry points implementedCancellation-entry consistency and DTO/reason/retryable regressions were not rerun; a standalone Tasks panel is outside the current implementation.
Web stateapps/web/lib/net/use-ws.ts: job_status, spawn_job_result; apps/web/components/right-sidebar/branches/index.tsx and attach-card.tsx: state/resource subscriptions.Major user surfaces implementedA standalone Tasks panel is outside current implementation; remaining resources, queue position, unknown cost, and recovery-state regressions were not rerun.
ResourceGovernor admission/lease/reconcileopenprogram/agent/resource_governance/: admit_job, claim_next, renew_lease, reconcile, scope, and resource view; integrated with runner.Primitives and major execution integration implementedThe token/cost provider boundary still needs adapter-by-adapter confirmation; full verification was not rerun.
Verification boundaryCurrent documentation and source sampling provide path/symbol evidence; historical verification remains in Git.Not reverifiedDocumentation build and checks are separate from product execution acceptance. This review did not rerun product runtime gates; that does not establish missing implementation.

Status rule.“Primitive exists,” “entry returns state,” and “complete execution path passed acceptance” are distinct states. Future updates must record each separately, using current worktree evidence rather than historical reviews or candidate-branch records.

Evidence index

TopicRepository evidence
JobRunner, single worker, cancellation, and orphan recoveryopenprogram/agent/job/runner/; openprogram/agent/job/store.py; openprogram/agent/job/types.py; openprogram/worker/paths.py
Spawn and message budgetsopenprogram/programs/tools/agents/agent/agent/agent.py; openprogram/programs/tools/agents/send_message/send_message/depth.py; openprogram/config_schema.py
Usage and provider recordingopenprogram/usage/event.py; openprogram/usage/ledger.py; openprogram/usage/recorder.py; openprogram/providers/stream.py
Deadline and function timeoutopenprogram/providers/utils/deadline.py; openprogram/programs/_runtime.py
Task UI and protocolapps/server/openprogram_server/_webui/ws_actions/job.py, Web branch/task cards, openprogram/programs/tools/agents/agent/list_jobs/, job_output/, JobRunner.cancel_execution
Competitors and existing designsagent-collab-comparison.html; Agent collaboration design; Async Job lifecycle; Usage metering