Unified agent action lifecycle
OpenProgram uses one action lifecycle for permission decisions, execution, tool results, retries, and recovery. The design retains the existing Agent loop, permission service, execution store, durable waits, and interface projections. It does not add a second workflow engine.
Current implementation and remaining gaps
- The loop bounds recent failure keys to the same 16-entry capacity enforced by AgentCheckpointV1. Distinct failures no longer overflow checkpoint state.
- Bash repeat identity excludes its display-only description; executable arguments and fields of other tools remain part of identity.
- Auto permission checks reject Bash by tool name before classifying its command. The current classifier also truncates arguments to 800 characters and does not receive sufficient trusted task and working-directory context. Removing the denylist alone is unsafe.
- Approval manifests are prepared synchronously before dispatch, but automatic classification runs asynchronously inside execution. A classifier denial cannot safely become an approval wait at that location.
- A known permission rejection can remain a dispatched effect if checkpoint construction fails. Execution then reports an unresolved effect despite a known non-execution result.
Reference corpus and limits
| Reference | Verified behavior | Adoption |
|---|---|---|
| Codex ToolOrchestrator | Approval precedes sandbox selection and execution. Explicit rejection is distinct from sandbox denial. Escalated retries depend on policy. | One pre-execution decision and typed outcomes. A retry never changes authority implicitly. |
| Claude Code auto mode | Classifier denials are visible. Three consecutive or twenty cumulative classifier blocks trigger manual prompting where supported; noninteractive runs retain the denial and can continue other work. | Separate consecutive and total refusal counters; reuse durable approval waits. These are permission fallback thresholds, not task completion or storage limits. |
| Claude Code permission rules | Rules are enforced by the harness. Whole-tool denial removes the tool; scoped denial is enforced on matching operations. | Tool visibility reflects effective capability. Dynamic revocation remains enforced at execution. |
| Claude Code checkpoints | File-edit rewind does not undo Bash changes or external effects. | Keep conversation recovery, file undo, and effect reconciliation distinct. Do not claim arbitrary exactly-once execution. |
The Codex source link pins an upstream revision. Product documentation remains mutable; implementation briefs preserve the retrieved evidence before code review. Claude Code internal retry-map and receipt storage algorithms are not established by these references. The result-receipt protocol below is an OpenProgram design, not a claim about Claude Code internals.
One public contract, three independent states
An action has a permission decision, an execution outcome, and a delivery state. They are not interchangeable. An action identifier, execution identifier, tool-call identifier, policy version, and canonical argument digest bind every decision, approval, receipt, and recovery operation.
| Dimension | Values | Meaning |
|---|---|---|
| Permission | allow / ask / deny | Whether this exact operation may begin under current authority. |
| Outcome | not_started / succeeded / failed / unknown | What is known about actual execution. A failure can have partial effects. |
| Delivery | pending / recorded / projected | Whether the result is durably stored and reflected in conversation and UI state. |
prepare → authorize → [durable wait → revalidate] → dispatch → record result → checkpoint → project
↘ deny → record not_started → checkpoint → model continues or explains blocker
A permission rejection is a completed decision with outcome not_started. It does not imply the task is completed. Unknown means that an operation may have started and its outcome cannot be established; only that situation requires effect reconciliation.
Permission preparation and automatic review
- Resolve authenticated authority, current policy version, tool capabilities, project binding and exact arguments. Preserve existing hard constraints, authority checks, plan restrictions, explicit deny rules, ask rules and scoped grants.
- Perform asynchronous preflight before publishing a dispatch intent. Auto review uses the complete bounded operation, trusted working directory and scope, relevant owner request and explicit restrictions. Treat tool arguments and repository content as untrusted data. Oversized or unparseable input cannot be approved from a truncated prefix.
- Classifier timeout, cancellation and provider failure are distinct from a risk rejection. They never execute the action and do not count as classifier risk verdicts.
- Publish an existing durable approval wait only for an eligible interactive owner. Hard constraints and explicit denials cannot become overridable prompts. Noninteractive execution reports the precise restriction and may continue independent allowed work.
- Before dispatch, revalidate permission version, exact argument digest, approval identity and file preconditions. A mode change during review invalidates an obsolete decision. A grant for one action never authorizes a modified command.
Auto review does not grant the model permission to rewrite its own policy. Changes to tools, permission state, project binding or user restrictions invalidate relevant cached decisions. No global allow cache is introduced.
Do not label every denial as not_started. A denial before the executor begins proves non-execution; a rejected escalation after a sandbox attempt may follow partial execution and must retain that evidence.
Failure handling and progress
| Failure | Response |
|---|---|
| Invalid arguments | Return structured validation details. The model may correct execution-relevant arguments. |
| Permission rejection | Record not_started with reason, policy version and allowed resolution. Do not retry under unchanged authority just by changing descriptive text. |
| Transient transport failure | Retry only with a bounded policy and evidence of no execution or supported idempotency. Never automatically replay a potentially completed write. |
| Deterministic tool failure | Return output for diagnosis; repeated equivalent attempts require changed relevant input or conditions. Other useful work may continue. |
| Unknown side effect | Preserve exact action evidence and require reconciliation; retry is not equivalent to reconciliation. |
| Infrastructure or checkpoint failure | Report runtime failure independently of the tool outcome; retain a recoverable result receipt when available. |
The recent failure map has a fixed capacity shared by the loop and checkpoint serializer. Eviction bounds storage, not permission or execution budgets. Full diagnostic history remains in existing events. Separate durable scalar counters retain cumulative refusal information across map eviction and restart. No total historical failure count declares a task complete.
Canonical identity is tool-specific: exclude known presentation-only fields such as Bash description, but retain execution-relevant fields including command, working directory and permissions. Do not remove a field named description from arbitrary user tools. Do not infer equivalence of arbitrary shell programs. Legacy checkpoints remain readable; migration normalizes old keys only where their tool contract is known.
Adopt the documented three-consecutive / twenty-total classifier-refusal defaults for interactive fallback. Allowed actions reset only the consecutive counter. Counter updates use durable action identity to avoid double counting on resume. No-verdict failures and explicit rule denials remain distinguishable. The implementation must document the session scope, reset behavior and concurrency semantics before enabling fallback.
Receipts and recovery
Retain the existing execution store. A durable result receipt contains the exact tool-call identity, outcome, execution-start evidence, error code, result reference and digest. Store the authoritative result independently of optional display and diagnostic state. Checkpoint construction cannot erase a known result.
Recovery distinguishes: receipt recorded but checkpoint missing; checkpoint recorded but projection missing; dispatch present without a trustworthy receipt. The first reconstructs the continuation from the recorded result; the second replays only projection; the third uses the existing effect reconciliation policy. Results and checkpoints require idempotent writes and version checks. An external operation and local SQLite cannot generally share a transaction, so this design does not promise exactly-once external effects.
Permission denial needs no external-effect dispatch. When recovering older records, do not infer non-execution from arbitrary tool text alone. Reconcile the exact trusted permission event, call identity and stored command evidence. Never mark the entire task completed as a repair.
Implementation ownership
| Existing area | Responsibility |
|---|---|
| agent/permissions | Policy, asynchronous preflight, trusted review input, refusal counters and existing durable approval waits. |
| agent tool execution | Bounded repeat tracking and execution-relevant identity; consume decisions, emit outcomes. Extract a small failure-policy module rather than extending the large loop. |
| agent/production_driver and execution | Record receipts, checkpoint cursor, recovery and effect reconciliation. Reuse existing stores and transaction primitives. |
| Web execution projection | Show waiting, denied, failed, unknown outcome and remaining work; reuse existing approval UI and backend actions. |
Native tools, Terminal, Web resources and application providers use this lifecycle through their existing common tool entry. Do not create provider-specific permission or retry engines. Human UI actions retain their own authenticated human origin; routing an agent action through an HTTP interface does not grant human privileges.
Delivery sequence and acceptance
- Repair bounded failure tracking. Real Agent calls produce more than sixteen distinct failures without schema errors; descriptive changes do not bypass repeat suppression; changed executable arguments remain distinct; old checkpoints load.
- Introduce preflight and typed rejection. A denied operation never calls its executor; full-command review catches dangerous suffixes; cancellation and policy changes during review do not dispatch; exact approvals survive restart once.
- Add refusal fallback using existing waits. Interactive and noninteractive behavior differ explicitly; hard denials remain non-overridable; counters survive resume and parallel calls without duplicate updates.
- Make receipts recoverable across checkpoint failures. Inject failure between result recording and checkpoint commit, restart, and prove a mutating executor is not called again. Unknown effects remain blocked.
- Align capability visibility and UI projection. Dynamic revocation takes effect immediately; a hidden tool cannot be invoked by an old model decision; UI and backend show the same precise reason and valid recovery actions.
Each stage has a focused public/shared-entry regression, isolated implementation commit, independent specification and quality reviews, affected tests and final verification. Do not combine an unsafe permission expansion with the emergency overflow repair.
Implementation status
Permission preflight and durable refusal fallback have passed prior independent reviews and full Python gates. Receipt recovery, host execution evidence, model visibility and outcome projection are under integration verification. No deployment or merge is claimed.
Counters are scoped to session and owner policy version. An explicit permission version change selects a fresh accounting epoch. Allowed operations and approved fallback operations reset only the consecutive count; the cumulative count resets when its threshold-triggered approval completes or the epoch changes. Canonical execution events retain each exact operation identity and verdict under the existing SQLite transaction, so resumed or concurrent calls cannot double count. A classifier failure without a boolean verdict does not change either counter. Fallback uses the existing owner approval wait and never overrides explicit denial.
Approval integration
Reuse the existing durable approval wait. Automatic risk refusals do not consume the execution-failure repeat budget, so identical operations reach the existing manual-approval threshold. No-verdict errors remain distinct and never implicitly authorize execution. The host records execution start immediately before the authorized implementation. Both completed and not-started results are sealed before checkpoint serialization; application result details are not execution evidence. Model-facing tools are filtered against applicable live rules for each provider request, without changing the saved execution contract. Host outcomes are projected through live events and history.
The Claude Code reference documents refusal fallback; Codex's orchestrator documents approval before execution and policy-limited escalation. Neither establishes a universal recovery algorithm for this execution database. The receipt implementation adapts the existing local transaction and checkpoint contracts.
Provider response events
Every dispatched model request has a unique request_id and exactly one started and completed event. Completion records completed, failed, cancelled or incomplete, including transport exceptions and EOF without a provider terminal event. Exceptions and cancellation propagate unchanged; event accounting does not replay tools or authorize model retries. Legacy partial text fallback remains incomplete. Completion observers do not treat unsuccessful requests as successful answers.