agents/openai.yaml
interface:
display_name: "AI Coding Agents — Tasks"
short_description: "Design Loop and Graph Engineering task runtimes"
default_prompt: "Use $ai-coding-agents-tasks to design Loop Engineering and Graph Engineering runtime surfaces, including task types, cyclic or workflow graphs, background execution, cancellation, and teammate coordination."
data/sources.json
{
"metadata": {
"skill": "ai-coding-agents-tasks",
"title": "AI Coding Agents Tasks - Sources",
"description": "Official documentation and implementation references for coding-agent task models, task lists, background execution, and teammate coordination",
"last_updated": "2026-08-10",
"updated": "2026-08-10",
"total_sources": 20,
"version": "1.1"
},
"categories": {
"official_documentation": [
{
"name": "Claude Code Documentation",
"url": "https://code.claude.com/docs/en/",
"type": "documentation",
"relevance": "Primary runtime documentation for Claude Code operational behavior",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code Slash Commands",
"url": "https://code.claude.com/docs/en/slash-commands",
"type": "documentation",
"relevance": "Official command and prompt-surface documentation relevant to task launch, task discovery, and interactive coordination flows",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code Subagents",
"url": "https://code.claude.com/docs/en/sub-agents",
"type": "documentation",
"relevance": "Official subagent delegation model for task handoff, worker context isolation, and teammate-style coordination",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
}
],
"implementation_references": [
{
"name": "Claude Code GitHub Repository",
"url": "https://github.com/anthropics/claude-code",
"type": "repository",
"relevance": "Authoritative implementation reference for task types, background execution, task dialogs, and task-list watching",
"update_frequency": "weekly",
"access": "free",
"add_as_web_search": false
},
{
"name": "Anthropic: Building Effective Agents",
"url": "https://www.anthropic.com/engineering/building-effective-agents",
"type": "guide",
"relevance": "High-level context for structuring durable agent workflows and delegated work",
"update_frequency": "quarterly",
"access": "free",
"add_as_web_search": true
}
],
"cross_runtime_references": [
{
"name": "Codex CLI Documentation",
"url": "https://github.com/openai/codex",
"type": "documentation",
"relevance": "Cross-runtime comparison point for background work and agent task patterns",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex Cloud Tasks CLI Source",
"url": "https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/cloud-tasks/src/cli.rs",
"type": "repository_source",
"relevance": "Pinned first-party source for cloud task commands, attempt bounds, status/list/apply/diff surfaces, branch and environment fields",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex Cloud Tasks App Source",
"url": "https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/cloud-tasks/src/app.rs",
"type": "repository_source",
"relevance": "Pinned first-party source for task apply result levels, skipped/conflict paths, and local materialization behavior",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex Agent Graph Store Source",
"url": "https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/agent-graph-store/src/store.rs",
"type": "repository_source",
"relevance": "Pinned first-party source for persisted parent-child thread topology, stable ordering, status updates, and breadth-first descendant traversal",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Agents SDK Handoffs",
"url": "https://openai.github.io/openai-agents-python/handoffs/",
"type": "documentation",
"relevance": "Official cross-runtime handoff model for delegated task execution and worker routing",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "OpenAI Agents SDK Sessions",
"url": "https://openai.github.io/openai-agents-python/sessions/",
"type": "documentation",
"relevance": "Official session-state reference for task continuity, persistence, and resume behavior across runs",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Chris Hayduk — Using Codex Goals Effectively",
"url": "https://x.com/ChrisHayduk/status/2053807198870880743",
"type": "first_party_practitioner_post",
"relevance": "First-party (OpenAI) guidance on Codex /goal command: quantitative-goal pattern, checklist-as-score trick, tight feedback loops, and 3-file journaling for multi-day runs. Source for the 'Goal-Mode Loops' section.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": false,
"captured": "2026-05-12"
},
{
"name": "Jarrod Watts — You Need More Than a Ralph Loop",
"url": "https://x.com/jarrodwatts/status/2052372045829382430",
"type": "practitioner_post",
"relevance": "Critique of bare goal-mode/ralph loops. Source for the 'When bare goal-mode is not enough' subsection: ambiguity bottleneck, single-context-window bottleneck, cross-context memory bottleneck, and pointers to orchestrator+reviewer triad and Watts 4-file journaling. Companion github skill: https://github.com/jarrodwatts/long-running-agent-skill",
"update_frequency": "static",
"access": "free",
"add_as_web_search": false,
"captured": "2026-05-12"
},
{
"name": "Anthropic: Introducing Routines in Claude Code",
"url": "https://claude.com/blog/introducing-routines-in-claude-code",
"type": "product_post",
"relevance": "First-party source for published Routines daily run caps: Pro 5/day, Max 15/day, Team/Enterprise 25/day with overage billing, and the one-off-run exemption from the daily cap. Source for the Limits and Caps section.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true,
"captured": "2026-07-11"
},
{
"name": "Claude Code Routines Docs",
"url": "https://code.claude.com/docs/en/routines",
"type": "documentation",
"relevance": "Current canonical Routines reference; confirms research-preview status, daily caps, and one-off-run exemption as of 2026-07-11.",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true,
"captured": "2026-07-11"
},
{
"name": "Claude Code Changelog — v2.1.198 (background subagents by default)",
"url": "https://code.claude.com/docs/en/changelog",
"type": "changelog",
"relevance": "First-party source that subagent spawns run in the background by default as of 2026-07-01, with Notification hook events (agent_needs_input / agent_completed) on completion or input-needed. Source for Background-Subagent Task Semantics.",
"update_frequency": "weekly",
"access": "free",
"add_as_web_search": false,
"captured": "2026-07-11"
},
{
"name": "Claude Code Changelog — v2.1.186 (background permission-prompt routing)",
"url": "https://code.claude.com/docs/en/changelog",
"type": "changelog",
"relevance": "First-party source that background subagent permission prompts surface into the parent/lead session (2026-06-22) instead of silently auto-denying. Corrects an earlier auto-deny-only description of background-subagent behavior. Source for Background-Subagent Task Semantics.",
"update_frequency": "weekly",
"access": "free",
"add_as_web_search": false,
"captured": "2026-07-11"
},
{
"name": "Claude Code Subagents Docs — Task-to-Agent rename",
"url": "https://code.claude.com/docs/en/sub-agents",
"type": "documentation",
"relevance": "Confirms the subagent-spawn tool was renamed Task -> Agent in v2.1.63, with Task(...) preserved as a legacy alias. Source for the task-types-and-lifecycle.md naming note.",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true,
"captured": "2026-07-11"
},
{
"name": "Claude Code Workflows Documentation",
"url": "https://code.claude.com/docs/en/workflows",
"type": "documentation",
"relevance": "Code-mode orchestration surface: loop and fan-out primitives absent from Codex CLI",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Codex CLI Multi-Agent v2 vs Claude Dynamic Workflows",
"url": "https://codex.danielvaughan.com/2026/06/08/parallel-subagent-race-codex-cli-multi-agent-v2-claude-dynamic-workflows-architecture-comparison/",
"type": "documentation",
"relevance": "Runtime asymmetry reference: TOML roles, max_depth/max_threads defaults, spawn_agents_on_csv",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
}
]
}
}
learnings.consolidated.md
# ai-coding-agents-tasks — Consolidated Learnings
Curated, dated, committed memory for this skill. Pruned from raw `learnings.md` via `agents-skills-feedback-loop/scripts/consolidate.py`. Human-approved.
Cap: 60 entries. When exceeded, promote durable rules to `references/`.
## Filter Override
<!-- Add 2-4 bullets that sharpen what counts as a learning for this skill. Leave empty to use the default filter from agents-skills-feedback-loop/references/learnings-format.md. -->
## Patterns That Work
## Mistakes to Avoid
## Domain Knowledge
## Open Questions
## Consolidated Principles
learnings.md
# ai-coding-agents-tasks — Learnings
## Patterns That Work
## Mistakes to Avoid
## Domain Knowledge
- [2026-07-11] Claude Code: Task tool renamed Agent (v2.1.63); subagents run background-by-default (v2.1.198); background permission prompts now route to the parent session instead of auto-denying (v2.1.186) — verified 2026-07-11.
## Open Questions
## Consolidated Principles
references/claude-code-routines.md
# Claude Code Routines
## Table of Contents
- [When Routines Fit](#when-routines-fit)
- [The Three Trigger Types](#the-three-trigger-types)
- [Prompt Authoring Rules](#prompt-authoring-rules)
- [Limits and Caps](#limits-and-caps)
- [Creation Paths](#creation-paths)
- [Comparison With Adjacent Tools](#comparison-with-adjacent-tools)
- [Host-Side Design Implications](#host-side-design-implications)
- [See Also](#see-also)
Routines are Anthropic-cloud-hosted task configurations that run without a live session. A routine is a saved (prompt, repositories, connectors) tuple that activates via **schedule**, **API call**, or **GitHub event** — independently or combined on the same routine.
Routines sit between `/loop` (session-bound, local) and full CI infrastructure (GitHub Actions, n8n, cron). They are the only coding-agent task type that runs when the user's machine is off.
**Status:** volatile hosted automation surface, still in research preview as of 2026-07-11 (not GA — re-check before assuming a stable contract). Reviewed against `code.claude.com/docs/en/routines` and the Anthropic routines blog post on 2026-07-11; exact caps, headers, endpoint fields, and account/team semantics must be re-verified in the live Claude Code docs or UI before shipping an integration.
## When Routines Fit
| Use routines when | Use something else when |
|---|---|
| Work must run while laptop is off | Task is inherently interactive |
| Trigger is a schedule, a webhook, or a monitoring alert | You need a live pair-programming loop → use a normal session |
| Job requires Claude reasoning over code/issues/logs | Job is pure build/test/deploy → GitHub Actions |
| Output is a labelled issue, a draft PR, a Slack summary | You need strict timing (routines run "sometime in the window") |
| Connectors (Slack, Sentry, Linear, GitHub) already integrated | You need <1h cadence → minimum interval is 1 hour |
## The Three Trigger Types
### 1. Schedule
Cadence presets: hourly, daily, weekdays, weekly, plus **custom cron** (minimum 1-hour interval; sub-hourly expressions rejected). Timezone-aware; scheduled time is entered in local zone. Actual start can lag a few minutes behind the target — design for "sometime in the window," not exact-second precision.
Canonical use: nightly backlog grooming, morning release-note draft, weekly dependency audit, hourly backlog stale-check.
### 2. API (`/fire` endpoint)
Each routine exposes a dedicated endpoint. Bearer token is shown **exactly once at generation** — store it immediately.
```bash
POST https://api.anthropic.com/v1/claude_code/routines/{trigger_id}/fire
curl -X POST https://api.anthropic.com/v1/claude_code/routines/{trigger_id}/fire \
-H "Authorization: Bearer {your_token}" \
-H "anthropic-beta: {current_routine_beta_header}" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{"text": "Production alert: error rate on /api/checkout exceeded 5% threshold. Alert ID: ALR-4821."}'
```
Response contains `claude_code_session_id` and `claude_code_session_url`. Log the URL — it opens the live run for inspection or manual continuation.
Three non-obvious facts:
- `text` is a **literal string**. JSON inside it is read as prose, not structured data. Write it as sentences.
- Each token is scoped to **one routine**. Rotate per-routine.
- The beta header can rotate. Do not hardcode a dated header in product code; store it as configuration with a visible last-verified date and fail closed when Anthropic rejects it.
Canonical use: alert → routine (Sentry posts stack trace as `text`, routine opens draft fix PR), deploy webhook → verification routine, external cron → routine.
### 3. GitHub Events
Supported: `pull_request` (opened, closed, assigned, labeled, synchronized, etc.) and `release` (created, published, edited, deleted).
Setup requires **both** steps (easy to stop at the first):
1. `/web-setup` in Claude Code → grants repository clone access
2. **Install the Claude GitHub App** on the target repo → enables webhook delivery
Filters (all conditions AND): Author, Title, Body, Base branch, Head branch, Labels, Is draft, Is merged, From fork.
**Regex gotcha:** `matches regex` tests the **entire** field. To match PR titles *containing* "hotfix", write `.*hotfix.*`. For substring intent, prefer `contains`.
**Branch permissions:** Claude pushes only to `claude/`-prefixed branches by default. To push elsewhere, enable "Allow unrestricted branch pushes" in routine settings. Commits appear under the routine owner's personal GitHub identity, not a bot account.
**Session model:** Each matching event starts a **fresh** session with no carryover from previous runs. Prompts must be self-contained per event.
## Prompt Authoring Rules
Routines run without approval prompts at every step. The prompt carries the full cognitive load.
| Rule | Why |
|---|---|
| Specify what "done" looks like | No human is there to notice a half-done run |
| Name specific connectors | Don't assume Claude knows which Slack workspace or Sentry project |
| Describe fallback when something unexpected happens | No one to re-prompt mid-run |
| State "do not do X" boundaries | No approval gate to catch scope creep |
| Self-contained per event (GitHub trigger) | No session state carries over |
**Bad:** "Check for issues."
**Good:** "Read all GitHub issues opened today in `{repo}`. For each: apply a label from [bug, feature, docs, question, needs-triage], assign based on CODEOWNERS for referenced files, post summary to `#dev-standup` with totals and breakdown. If zero issues, post `No new issues today.`"
## Limits and Caps
| Cap | Behaviour when hit |
|---|---|
| Daily run cap (per plan) | Rejected until window resets; metered-usage plans can continue on overage billing |
| GitHub per-routine hourly cap | Events **dropped**, not queued — gone until next window |
| Minimum schedule interval | 1 hour; sub-hourly cron rejected |
Published per-plan daily caps (verified 2026-07-11, `code.claude.com/docs/en/routines`): Pro 5/day, Max 15/day, Team and Enterprise 25/day. Live remaining count is at `claude.ai/code/routines` or `claude.ai/settings/usage` — caps can change without notice while the feature is in research preview, so treat the numbers above as a snapshot, not a contract.
**One-off runs are exempt.** A manually-fired one-off run (not on a recurring schedule) draws down normal session usage but does not count against the daily routine cap — model this as a separate counter from the recurring-schedule cap if you build quota-aware tooling on top of routines.
**Individual ownership only.** Routines belong to a personal `claude.ai` account during research preview. No team sharing, no transfer, no co-ownership. Teammates needing the same routine each set up a copy.
## Creation Paths
| Path | Supports |
|---|---|
| Web UI at `claude.ai/code/routines` → New Routine | All three trigger types (canonical) |
| CLI `/schedule` inside a session | Schedule trigger only; add API/GitHub in web UI afterward |
| Desktop app → New Task > New Remote Task | All three (distinct from local Desktop scheduled tasks) |
All three create the same underlying routine object.
## Comparison With Adjacent Tools
| Tool | Best for | Avoid when |
|---|---|---|
| **Routines** | AI reasoning on dev artefacts (diffs, issues, logs) while laptop is off | Strict sub-hourly timing; team-shared automation |
| GitHub Actions | Build/test/deploy pipelines, language-agnostic CI | Job requires code-aware reasoning |
| n8n / Zapier | Connecting 10+ SaaS tools without code | Job requires reading and modifying code |
| cron | Simple local scripts producing clean output | Job needs judgement or fresh context |
| `/loop` | Self-paced or recurring work **inside** a live session | Work must survive session end |
For most teams the answer is **Routines + Actions**: Actions run the pipeline, routines reason about what ran.
## Host-Side Design Implications
When modelling routines in a coding-agent runtime:
- Treat routine runs as a **remote task type** distinct from local shell and local agent tasks — they have a cloud-owned lifecycle, a session URL surface, and cannot be cancelled by closing the terminal.
- Trigger metadata (cron expression, event filter, API token ID) belongs in task state, not UI only.
- Each GitHub-event run produces a fresh session; link rather than merge transcripts when showing history.
- Cap behaviour is **drop, not queue** for GitHub events — surface this in UI so users tune filters instead of expecting replays.
- Beta-header rotation is a first-class migration concern; store the header version and verification date the routine was authored against so you can warn on upcoming rotation.
## See Also
- [`task-types-and-lifecycle.md`](task-types-and-lifecycle.md) — where routines fit in the task-family taxonomy
- [`../../ai-coding-agents-remote-runtime/SKILL.md`](../../ai-coding-agents-remote-runtime/SKILL.md) — routines as the canonical Anthropic-cloud remote runtime
- [`../../ai-coding-agents-sessions/SKILL.md`](../../ai-coding-agents-sessions/SKILL.md) — fresh-session-per-event session model
- [`../../ops-incident-response/SKILL.md`](../../ops-incident-response/SKILL.md) — alert → `/fire` integration pattern
references/durable-trigger-integration.md
# Durable Trigger Integration
Use this reference when an agent invocation must be **durable**: the orchestrator survives process crashes, retries with exactly-once semantics, supports multi-step compensation, and replays history on restart.
Substrate choice in May 2026: Temporal, Inngest, Restate, Trigger.dev, AWS Step Functions. All four implement durable execution; pick by ecosystem, not features.
Pair this reference with [`webhook-and-queue-triggers.md`](webhook-and-queue-triggers.md): use queue triggers when fire-and-forget is fine, use durable triggers when correctness across failures matters.
## Table of Contents
- [When You Need Durable Execution](#when-you-need-durable-execution)
- [Substrate Comparison (May 2026)](#substrate-comparison-may-2026)
- [The Agent-as-Activity Pattern](#the-agent-as-activity-pattern)
- [Temporal Integration](#temporal-integration)
- [Inngest Integration](#inngest-integration)
- [Restate Integration](#restate-integration)
- [AWS Step Functions Integration](#aws-step-functions-integration)
- [Replay Semantics](#replay-semantics)
- [Compensation and Sagas](#compensation-and-sagas)
- [Long-Running Agents](#long-running-agents)
- [Signals and Human-in-the-Loop](#signals-and-human-in-the-loop)
- [Observability](#observability)
- [Operational Checklist](#operational-checklist)
- [Cross-References](#cross-references)
## When You Need Durable Execution
Use durable triggers when **any** of the following hold:
- The agent's work has side effects that must not happen twice (charges, sends, deletes).
- The work is multi-step and must finish all steps or none.
- A single agent run can exceed 15 minutes (Lambda cap) or even hours.
- You need to send a signal to a running agent (human approval, cancel, update parameters).
- The work spans services or providers and needs compensation on failure.
- You need to replay a past run for audit, debugging, or recovery.
Use plain queue triggers from [`webhook-and-queue-triggers.md`](webhook-and-queue-triggers.md) when:
- The work is idempotent and short (<5 minutes).
- A failed run is acceptable to retry from scratch.
- No human-in-the-loop signal is needed.
## Substrate Comparison (May 2026)
| Substrate | Type | Hosted Option | Best For | Caution |
|---|---|---|---|---|
| **Temporal** | Workflow engine, polyglot SDKs | Temporal Cloud | Complex multi-step agent orchestration, long-running workflows | Self-hosting non-trivial; learning curve |
| **Inngest** | Event-driven durable functions | Inngest Cloud + self-host | Event-driven agent pipelines, fan-out, debouncing | Less expressive than Temporal for branching workflows |
| **Restate** | Distributed RPC + durable state | Restate Cloud + self-host | Agent-as-service with durable state, low operational overhead | Newer ecosystem, smaller community |
| **Trigger.dev** | Background jobs, TypeScript-first | Trigger.dev Cloud + self-host | Node/TS agent backends, dev ergonomics | Less mature multi-region story |
| **AWS Step Functions** | State-machine orchestration | AWS-hosted only | AWS-native stacks, integration with Bedrock | Verbose JSON; not portable |
| **DBOS** | Postgres-native durable execution | Self-host | Python/TS teams already on Postgres | Newer; fewer enterprise references |
If you don't know which to pick: Temporal for backend-heavy stacks, Inngest for product/event-driven stacks, Trigger.dev for full-TS shops.
## The Agent-as-Activity Pattern
The single most important pattern: **the agent invocation is an activity, not a workflow**.
```text
Workflow (durable, replay-safe, deterministic)
├─ activity: load_context()
├─ activity: invoke_agent(prompt, context) ← the LLM call lives here
├─ activity: validate_output(result)
├─ activity: apply_side_effects(result)
└─ activity: notify(result)
```
Why:
- Workflows must be deterministic for replay. LLM outputs are non-deterministic — they cannot be replayed.
- Activities are recorded once; their output becomes part of workflow history. On replay, the recorded output is returned without re-invoking the LLM.
- This is the only safe way to retry workflow steps without re-paying for the LLM call.
Rule of thumb: anything non-deterministic, slow, or expensive lives in an activity. The workflow is just glue.
## Temporal Integration
```python
# activities.py
from temporalio import activity
from anthropic import Anthropic
@activity.defn
async def invoke_agent(prompt: str, context: dict) -> dict:
client = Anthropic()
response = await client.messages.create(
model=CURRENT_MODEL_ID, # resolve from config; see ../../claude-api/SKILL.md for current model ids
max_tokens=4096,
system=context["system"],
messages=[{"role": "user", "content": prompt}],
)
return {
"text": response.content[0].text,
"tokens": response.usage.input_tokens + response.usage.output_tokens,
"model": response.model,
}
@activity.defn
async def apply_side_effects(result: dict) -> str:
# Idempotent side-effect with external transaction ID
txn_id = await db.execute(
"INSERT ... ON CONFLICT (idempotency_key) DO NOTHING RETURNING id",
result["idempotency_key"],
)
return txn_id
```
```python
# workflow.py
from datetime import timedelta
from temporalio import workflow
from temporalio.common import RetryPolicy
@workflow.defn
class AgentWorkflow:
@workflow.run
async def run(self, request: dict) -> dict:
context = await workflow.execute_activity(
load_context, request,
start_to_close_timeout=timedelta(minutes=1),
retry_policy=RetryPolicy(maximum_attempts=3),
)
agent_result = await workflow.execute_activity(
invoke_agent, args=[request["prompt"], context],
start_to_close_timeout=timedelta(minutes=10),
retry_policy=RetryPolicy(maximum_attempts=2, non_retryable_error_types=["BadRequest"]),
)
validation = await workflow.execute_activity(
validate_output, agent_result,
start_to_close_timeout=timedelta(minutes=1),
)
if not validation["passed"]:
# Wait for human approval
await workflow.wait_condition(lambda: self.human_approved is not None)
if not self.human_approved:
return {"status": "rejected", "result": agent_result}
txn_id = await workflow.execute_activity(
apply_side_effects, agent_result,
start_to_close_timeout=timedelta(minutes=2),
retry_policy=RetryPolicy(maximum_attempts=5),
)
return {"status": "ok", "txn_id": txn_id, "result": agent_result}
@workflow.signal
def approve(self, decision: bool):
self.human_approved = decision
human_approved: bool | None = None
```
Notes:
- `invoke_agent` activity timeout is generous (10 min) but bounded.
- Non-retryable error types prevent retrying on 400 / bad prompt errors.
- Signals (`approve`) implement human-in-the-loop without polling.
## Inngest Integration
```typescript
// inngest/functions.ts
import { inngest } from "./client";
import Anthropic from "@anthropic-ai/sdk";
export const agentFunction = inngest.createFunction(
{ id: "agent-handler", concurrency: 50 },
{ event: "agent/triggered" },
async ({ event, step }) => {
const context = await step.run("load-context", async () =>
loadContext(event.data.requestId)
);
const agentResult = await step.run("invoke-agent", async () => {
const client = new Anthropic();
const response = await client.messages.create({
model: CURRENT_MODEL_ID, // resolve from config; see ../../claude-api/SKILL.md for current model ids
max_tokens: 4096,
system: context.system,
messages: [{ role: "user", content: event.data.prompt }],
});
return {
text: response.content[0].text,
tokens: response.usage.input_tokens + response.usage.output_tokens,
};
});
const validation = await step.run("validate", async () =>
validateOutput(agentResult)
);
if (!validation.passed) {
// Wait for human approval via inngest event
const approval = await step.waitForEvent("approval", {
event: "agent/approved",
timeout: "24h",
match: "data.requestId",
});
if (!approval || !approval.data.decision) {
return { status: "rejected" };
}
}
const txnId = await step.run("apply-side-effects", async () =>
applySideEffects(agentResult)
);
return { status: "ok", txnId };
}
);
```
Inngest's `step.run` gives the same memoization guarantee as Temporal activities: each step's output is recorded and replayed on retry.
## Restate Integration
Restate's model: durable RPC handlers with built-in state. The agent becomes a virtual object.
```typescript
import * as restate from "@restatedev/restate-sdk";
import Anthropic from "@anthropic-ai/sdk";
const agentService = restate.service({
name: "agent",
handlers: {
handle: async (ctx: restate.Context, request: AgentRequest) => {
const context = await ctx.run("load-context", () => loadContext(request.id));
const result = await ctx.run("invoke-agent", async () => {
const client = new Anthropic();
const response = await client.messages.create({ /* ... */ });
return { text: response.content[0].text };
});
const validation = await ctx.run("validate", () => validate(result));
if (!validation.passed) {
const decision = await ctx.awakeable<boolean>();
// Approval flow notifies and resolves the awakeable
if (!(await decision.promise)) return { status: "rejected" };
}
return ctx.run("side-effect", () => applySideEffects(result));
},
},
});
```
Restate trade-off: simpler model than Temporal, less mature observability tooling.
## AWS Step Functions Integration
For AWS-native stacks, especially when the agent calls Bedrock:
```json
{
"Comment": "Agent workflow with approval gate",
"StartAt": "LoadContext",
"States": {
"LoadContext": {
"Type": "Task",
"Resource": "arn:aws:lambda:::function:load-context",
"Next": "InvokeAgent"
},
"InvokeAgent": {
"Type": "Task",
"Resource": "arn:aws:states:::bedrock:invokeModel",
"Parameters": {
"ModelId": "{current-bedrock-model-id}",
"Body.$": "$.payload"
},
"Retry": [{
"ErrorEquals": ["ThrottlingException"],
"IntervalSeconds": 2, "MaxAttempts": 5, "BackoffRate": 2.0
}],
"Next": "Validate"
},
"Validate": {
"Type": "Task",
"Resource": "arn:aws:lambda:::function:validate",
"Next": "CheckApproval"
},
"CheckApproval": {
"Type": "Choice",
"Choices": [{
"Variable": "$.validation.passed",
"BooleanEquals": false,
"Next": "WaitForApproval"
}],
"Default": "ApplySideEffects"
},
"WaitForApproval": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
"Parameters": {
"FunctionName": "send-approval-request",
"Payload": {"taskToken.$": "$$.Task.Token", "request.$": "$"}
},
"Next": "ApplySideEffects"
},
"ApplySideEffects": {
"Type": "Task",
"Resource": "arn:aws:lambda:::function:apply-side-effects",
"End": true
}
}
}
```
`waitForTaskToken` is the canonical AWS way to do human-in-the-loop without polling.
## Replay Semantics
When a workflow replays:
- Activity results are returned from history without re-execution.
- `workflow.sleep`, `workflow.wait_condition`, signal handlers replay deterministically.
- Any non-deterministic code in the workflow (random, time, network) **outside** an activity will break replay.
The contract: workflow code = orchestration only. Anything that touches the outside world is in an activity.
Test replay determinism by running the workflow twice from the same history. Temporal and Inngest have explicit replay tests; use them in CI.
## Compensation and Sagas
Multi-step agent work often needs rollback on failure: the agent created a draft, applied a partial state change, then a downstream step failed. The compensation pattern handles this.
```python
@workflow.defn
class AgentSagaWorkflow:
@workflow.run
async def run(self, req: dict) -> dict:
compensations = []
try:
draft = await workflow.execute_activity(create_draft, req, start_to_close_timeout=timedelta(minutes=2))
compensations.append(("delete_draft", draft["id"]))
preview = await workflow.execute_activity(generate_preview, draft, start_to_close_timeout=timedelta(minutes=5))
compensations.append(("delete_preview", preview["id"]))
published = await workflow.execute_activity(publish, preview, start_to_close_timeout=timedelta(minutes=2))
return {"status": "ok", "published_id": published["id"]}
except Exception as e:
for activity_name, target_id in reversed(compensations):
await workflow.execute_activity(activity_name, target_id, start_to_close_timeout=timedelta(minutes=1))
raise
```
The compensation list is built as forward progress happens. On failure, compensations run in reverse order. Each compensation must be idempotent.
## Long-Running Agents
An agent run that exceeds an hour needs additional patterns:
- **Heartbeating**: the activity sends periodic heartbeats. Workflow can detect a hung activity.
- **Continue-as-new**: when a workflow's history grows large (>10k events in Temporal), continue-as-new keeps it healthy.
- **External cancellation**: a signal handler that sets a cancellation flag the activity checks periodically.
```python
@activity.defn
async def long_running_agent(prompt: str) -> dict:
iters = 0
while iters < MAX_ITERS:
activity.heartbeat({"iter": iters})
if activity.is_cancelled():
return {"status": "cancelled", "iter": iters}
result = await call_llm_once(prompt)
if acceptance_met(result):
return result
iters += 1
return {"status": "exhausted"}
```
Long-running agent activities should be considered a Shape C use case — see [`../../ai-agents/references/autonomous-loop-patterns.md`](../../ai-agents/references/autonomous-loop-patterns.md).
## Signals and Human-in-the-Loop
Signals are the durable way to communicate with a running workflow:
- Approval / rejection
- Parameter updates ("increase budget", "change target")
- External cancellation
- Sub-task completion notifications
```python
@workflow.defn
class ApprovalGatedAgent:
@workflow.run
async def run(self, req: dict) -> dict:
result = await workflow.execute_activity(invoke_agent, req, start_to_close_timeout=timedelta(minutes=10))
await workflow.execute_activity(notify_human, result, start_to_close_timeout=timedelta(minutes=1))
try:
await workflow.wait_condition(lambda: self.decision is not None, timeout=timedelta(hours=24))
except TimeoutError:
return {"status": "timeout", "result": result}
return {"status": self.decision, "result": result}
@workflow.signal
def approve(self, decision: str):
self.decision = decision
decision: str | None = None
```
The 24h timeout matters: an approval workflow that waits forever is a leaked workflow.
## Observability
Per-workflow signals (all substrates expose these natively):
- workflow started / completed / failed / timed out
- activity started / completed / failed / retrying
- signal received
- workflow continue-as-new
Wire these into your standard telemetry. Most substrates have native exporters to OpenTelemetry.
Key dashboards:
- workflow success rate by type
- activity p50 / p99 latency
- retry rate per activity
- in-flight workflows by age (long-running detector)
- approval wait time distribution
## Operational Checklist
Before deploying a durable agent workflow:
- [ ] Agent invocation isolated to a single activity (deterministic replay)
- [ ] Activity timeout matches realistic worst-case agent runtime + buffer
- [ ] Non-retryable error types defined (bad input, auth failure)
- [ ] Idempotency at the side-effect activity level (db unique constraint or external txn ID)
- [ ] Compensations defined for any irreversible action that has a successor
- [ ] Signal handlers documented with expected payload shape
- [ ] Wait conditions have explicit timeouts
- [ ] Long-running activities heartbeat at < timeout / 3
- [ ] Continue-as-new path tested if history grows large
- [ ] Replay determinism tested in CI
- [ ] Workflow worker autoscaling configured
- [ ] Activity worker pool sized for concurrent LLM calls
## Cross-References
- [`webhook-and-queue-triggers.md`](webhook-and-queue-triggers.md) — simpler triggers when durability is not required
- [`task-types-and-lifecycle.md`](task-types-and-lifecycle.md) — task model background
- [`claude-code-routines.md`](claude-code-routines.md) — Anthropic-hosted routine specifics
- [`../../ai-agents/references/autonomous-loop-patterns.md`](../../ai-agents/references/autonomous-loop-patterns.md) — Shape C loops as durable workflows
- [`../../ai-agents/references/24-7-operating-model.md`](../../ai-agents/references/24-7-operating-model.md) — SLOs and oncall
- [`../../software-workflow-automation/references/durable-execution.md`](../../software-workflow-automation/references/durable-execution.md) — substrate deep-dive
- [`../../ai-agents/references/guardrails-implementation.md`](../../ai-agents/references/guardrails-implementation.md) — approval gates and policy
- [`../../ai-mlops/references/incident-response-playbooks.md`](../../ai-mlops/references/incident-response-playbooks.md) — incident response
references/loop-and-graph-runtime-surfaces.md
# Loop and Graph Runtime Surfaces
## Table of Contents
- [Why This Comparison Matters](#why-this-comparison-matters)
- [Surface Matrix](#surface-matrix)
- [Claude Code — Programmable Control Flow](#claude-code--programmable-control-flow)
- [Codex — Declarative Roles, Model-Held Control Flow](#codex--declarative-roles-model-held-control-flow)
- [Task Queue vs Cyclic Graph](#task-queue-vs-cyclic-graph)
- [Choosing A Runtime For Iterative Work](#choosing-a-runtime-for-iterative-work)
- [Portable Loop Contract](#portable-loop-contract)
- [Scheduled And Recurring Work](#scheduled-and-recurring-work)
- [Known Traps](#known-traps)
- [Verification Note](#verification-note)
- [Cross-References](#cross-references)
Where [task-types-and-lifecycle.md](task-types-and-lifecycle.md) models tasks as a **queue** — claim, run, complete — this reference covers what happens when the work is **iterative or cyclic**: loops that repeat until convergence, and graphs whose edges carry state between nodes. Where [openai-codex-cloud-tasks-and-agent-graph.md](openai-codex-cloud-tasks-and-agent-graph.md) covers Codex's persisted parent-child *topology*, this file covers **control flow** across both runtimes.
## Why This Comparison Matters
Claude Code and Codex diverged in mid-2026 on a dimension that determines how iterative work must be built. The difference is not cosmetic and not closable by prompt engineering:
- **Claude Code** can execute orchestration as **code**. Loops, branches, and fan-out live in a script the runtime runs.
- **Codex** orchestrates through **declarative role files plus turn-by-turn model decisions**. There is no code-mode loop primitive.
Design consequence: an iterative workflow that is ten lines of `while` on Claude Code becomes either a model-held loop (weak termination guarantee) or an external driver (extra infrastructure) on Codex. Teams targeting both runtimes must build to the weaker surface or accept per-runtime implementations.
## Surface Matrix
| Capability | Claude Code | Codex CLI |
|---|---|---|
| Code-mode orchestration script | Yes — Dynamic Workflows | No |
| Loop primitive in orchestration | Yes — JS `while`/`for` in script | No — lead-thread iteration only |
| Parallel fan-out | Subagents; `parallel()`/`pipeline()` in scripts | Subagents; `max_threads` (default ~6) |
| Direct worker-to-worker comms | Agent Teams | No — coordinate via parent |
| Recursive spawning | Bounded by depth rules | `max_depth = 1` default |
| Concurrency ceiling | ~16 concurrent, 1000 total per run | `max_threads` configurable |
| Role definition | Agent files / frontmatter | TOML in `.codex/agents/` |
| Persisted parent-child topology | Session and task state | Agent-graph store |
| Batch spawn from data | Script over a list | Experimental `spawn_agents_on_csv` |
| Deterministic replay / resume | Prefix-cached resume by run id | Task-level re-run |
Treat every row as verify-before-relying. Both runtimes moved substantially through 2026.
## Claude Code — Programmable Control Flow
Dynamic Workflows shipped **2026-05-28** alongside Opus 4.8 (CLI v2.1.154+). The orchestrator writes a small JavaScript program that the harness executes; the script spawns subagents, routes outputs between them, and holds the loops and conditionals itself.
Consequences for task-system design:
- **Intermediate results stay out of the lead's context.** A 200-item sweep keeps the lead's window at roughly final-answer size, because per-item reports live in script variables.
- **Termination becomes executable.** A loop predicate in code is a guarantee; a loop predicate in a prompt is an intention that decays as context fills.
- **Runs are resumable by prefix.** The unchanged prefix of agent calls returns cached results; the first edited call onward re-executes. Iterating on orchestration is cheap.
- **Scale is real and must be opted into.** One instruction can spawn dozens of agents, so this surface should never be entered by inference.
Full primitive semantics, barrier-vs-pipeline rules, and determinism constraints: [`../../agents-swarm-orchestration/references/scripted-workflows.md`](../../agents-swarm-orchestration/references/scripted-workflows.md).
## Codex — Declarative Roles, Model-Held Control Flow
Codex CLI Multi-Agent v2 (v0.137, **2026-06-04**) defines agents as TOML role files under `.codex/agents/`, with `/agent` for thread management. Orchestration is declarative in the roles and imperative in the model's turn-by-turn reasoning — there is no script layer that decides what runs next.
Building iterative work on Codex means choosing one of:
1. **Lead-thread iteration** — the model decides each round whether to continue. Weakest termination guarantee; pair with enforced caps.
2. **External driver** — a shell or Python wrapper invoking Codex non-interactively per iteration, holding the loop and the checkpoint. Strongest guarantee; most infrastructure. See [`../../ai-agents/references/autonomous-loop-patterns.md`](../../ai-agents/references/autonomous-loop-patterns.md).
3. **Durable workflow engine** — Temporal / Inngest / Restate with the agent as an activity, when the loop must survive crashes. See [durable-trigger-integration.md](durable-trigger-integration.md).
Defaults to respect: `max_depth = 1` (no recursion) and `max_threads` (~6). Raising depth is rarely correct — errors compound across levels uncaught.
## Task Queue vs Cyclic Graph
A queue and a cyclic graph are different data models, and conflating them is the most common design error in this area.
| | Task queue | Cyclic graph |
|---|---|---|
| Node revisits | No — a task completes once | Yes — nodes re-enter with new state |
| Edges carry | Nothing (ordering only) | Accumulated state |
| Termination | Queue drains | Predicate over state |
| Failure unit | One task | One pass; state persists |
| Naturally models | Independent work items | Refinement, discovery, feedback |
If work re-enters the same node with different state, a queue will model it as an unrelated new task and lose the accumulated context that made the second visit meaningful. Symptoms: rediscovering the same findings each round, no convergence signal, retry counts that mean nothing.
**Rule:** the moment a task's input depends on a prior *completion of the same task type*, you need graph state — an explicit accumulated-state object — not a second queue entry. Persist that state in files, not memory, so any new lead session resumes by reading it.
## Choosing A Runtime For Iterative Work
| Situation | Choose | Why |
|---|---|---|
| Many items, known stages, high intermediate volume | Claude Code scripted workflow | Deterministic flow; lead context stays small |
| Unknown extent, must converge, single runtime | Claude Code script loop | Executable termination predicate |
| Must survive crash / span hours or days | External driver or durable engine | Survives session death on either runtime |
| Codex-only shop, iterative work | External driver + enforced caps | No code-mode loop available |
| Cross-runtime portability required | Portable loop contract (below) | Build to the weaker surface |
| Judgment-dependent next step | Lead-thread on either runtime | Scripts cannot encode judgment |
## Portable Loop Contract
When the same iterative workflow must run on both runtimes, define the loop as data and keep the driver thin. Every field is required — a loop missing any of them is unbounded on at least one runtime:
```text
goal: <one-sentence objective>
progress: <what makes pass N+1 differ from pass N>
termination: <observable predicate over state>
hard_cap: <max iterations AND max tokens> # independent of predicate
on_cap: halt | escalate # never "continue"
state_file: <path — accumulated state, survives restart>
dedup_key: <function over surfaced items>
checkpoint: <path — written every pass>
```
Then implement the driver per runtime: a script loop on Claude Code, an external wrapper on Codex. The contract stays identical, so the workflow's behavior does not depend on which runtime executed it.
Two invariants worth restating because both runtimes make it easy to violate them:
- Deduplicate against **everything surfaced**, not everything confirmed. Dedup against confirmed results makes rejected items resurface every pass, and the loop never terminates.
- Enforce caps in **hooks or driver code**, not prompt instructions. Instructions decay with context; runtime enforcement does not.
## Scheduled And Recurring Work
Recurring work is a loop with time as the trigger. Both runtimes expose scheduling, and it interacts with task-store design: each firing is normally a fresh session, so **nothing carries over except what was written to files**.
Design rules:
- Treat the state file as the only continuity between firings. A scheduled loop that relies on session memory silently restarts from zero.
- Make each firing idempotent — a re-fire after a crash must not double-apply.
- Cap cumulative spend across firings, not just per firing. Per-run caps do not bound a schedule that fires hourly.
- Give recurring runs an explicit stop condition or expiry. A schedule with no end is an unbounded loop with a slow clock.
See [claude-code-routines.md](claude-code-routines.md) for trigger types and fresh-session semantics, and [webhook-and-queue-triggers.md](webhook-and-queue-triggers.md) for event-driven equivalents.
## Known Traps
| Trap | Consequence | Fix |
|---|---|---|
| Assuming Codex has a code-mode loop | Design does not build; late rework | Use external driver on Codex |
| Modeling cyclic refinement as queue entries | Accumulated state lost; no convergence | Explicit graph-state object in a file |
| Model-held loop with no enforced ceiling | Runs until context or budget dies | Hook or driver caps outside the agent |
| Dedup against confirmed results | Rejected items resurface forever | Dedup against all surfaced items |
| Raising Codex `max_depth` above 1 | Errors compound uncaught across levels | Keep at 1; verify at each level |
| Scheduled loop relying on session memory | Silent restart from zero each firing | State file is the only continuity |
| Per-firing cost cap on a recurring schedule | Unbounded cumulative spend | Cap cumulative across firings |
| Reading a cached empty result as "did not run" | Misdiagnosis during resume debugging | Read the run journal for actual returns |
| Porting a Claude Code script to Codex verbatim | No execution surface for it | Re-implement the driver; keep the contract |
## Verification Note
Version- and date-stamped claims here (Dynamic Workflows 2026-05-28 / CLI v2.1.154+; Codex Multi-Agent v2 v0.137 / 2026-06-04; concurrency and depth defaults) reflect the August 2026 check. Both runtimes iterate faster than this file. Re-verify against official release notes before committing an architecture, and mark these as unverified when web access is unavailable.
## Cross-References
- Scripted workflow primitives and constraints: [`../../agents-swarm-orchestration/references/scripted-workflows.md`](../../agents-swarm-orchestration/references/scripted-workflows.md)
- Loop shapes, termination, convergence detection: [`../../agents-swarm-orchestration/references/loop-orchestration.md`](../../agents-swarm-orchestration/references/loop-orchestration.md)
- Codex persisted parent-child topology and apply states: [openai-codex-cloud-tasks-and-agent-graph.md](openai-codex-cloud-tasks-and-agent-graph.md)
- Task families, statuses, background eligibility: [task-types-and-lifecycle.md](task-types-and-lifecycle.md)
- Crash-durable multi-step runs: [durable-trigger-integration.md](durable-trigger-integration.md)
- Hosted routines and fresh-session model: [claude-code-routines.md](claude-code-routines.md)
- Hook-enforced iteration and budget caps: [`../../agents-hooks/references/budget-and-loop-hooks.md`](../../agents-hooks/references/budget-and-loop-hooks.md)
- Shape C autonomous loops (PRD-driven, framework-neutral): [`../../ai-agents/references/autonomous-loop-patterns.md`](../../ai-agents/references/autonomous-loop-patterns.md)
- Graph-structured agent state and traversal: [`../../ai-agents/references/context-graph-patterns.md`](../../ai-agents/references/context-graph-patterns.md)
references/openai-codex-cloud-tasks-and-agent-graph.md
# OpenAI Codex Cloud Tasks and Agent Graph
Sources:
- OpenAI Codex repo, commit `9f42c89c0112771dc29100a6f3fc904049b2655f`
- `codex-rs/cloud-tasks/src/cli.rs`
- `codex-rs/cloud-tasks/src/app.rs`
- `codex-rs/agent-graph-store/src/store.rs`
Use this reference when designing hosted task queues, "best of N" task runs, apply/diff flows, or parent-child task topology for coding-agent runtimes.
## Table of Contents
- [What To Steal](#what-to-steal)
- [Agent Graph Store Pattern](#agent-graph-store-pattern)
- [Portable Runtime Contract](#portable-runtime-contract)
- [Tests To Require](#tests-to-require)
- [Source Links](#source-links)
## What To Steal
### Task CLI as lifecycle boundary
Codex models cloud tasks as explicit CLI operations, not only as chat commands:
- `exec` creates a task with prompt, branch, env, and attempt count.
- `status` inspects a known task.
- `list` is bounded by environment and limit.
- `apply` materializes a chosen task result into the local worktree.
- `diff` inspects a task result before applying.
This is a cleaner boundary than "background chat did something." A hosted coding-agent runtime should treat each task result as an artifact that can be inspected, diffed, and applied by a separate command path.
### Best-of-N attempts as a first-class field
Codex bounds task attempts in the CLI with a small range. The useful pattern is not the exact numeric limit; it is that "run several independent attempts" is part of the task request, not an external loop.
Design rule:
- Store `attempt_count` on the task.
- Attribute each attempt separately.
- Pick/apply one result explicitly.
- Keep abandoned attempts inspectable until retention expires.
Known trap:
- Do not merge all attempts into one transcript. That destroys comparison and blame when one attempt was useful and another was unsafe.
### Apply is not success/failure only
The Codex task app has explicit apply-result levels:
- success
- partial
- error
It also models skipped and conflicting paths. Import this state shape. "Apply failed" is too coarse for agent tasks because partial application can leave useful local files and unresolved conflicts.
Minimum apply result fields:
- `task_id`
- `attempt_id`
- `applied_paths`
- `skipped_paths`
- `conflict_paths`
- `result_level`
- `message`
Known trap:
- If partial apply is possible, the UI and task store must both represent it. A green terminal line with conflicted files is a misleading state.
### Environment-scoped listing
Codex task listing is environment-filtered. That pattern matters for multi-tenant or multi-repo hosts.
Task list queries should include:
- account or org
- repo or workspace
- branch
- environment
- limit
- sort key
Known trap:
- A global "recent tasks" list is useful for humans but dangerous as a runtime API. It can accidentally apply or cancel work from the wrong repo or environment.
## Agent Graph Store Pattern
Codex has a small persistent agent graph store for parent-child thread topology. The reusable shape:
- Store parent thread id, child thread id, task id, status, and timestamps.
- Upsert edges idempotently.
- Return children in stable ordering.
- Traverse descendants breadth-first.
- Let status updates target an existing edge.
This is the missing link between "spawn a subagent" and "understand the task tree later."
Design rule:
- Treat task topology as runtime state, not transcript decoration.
- Parent-child links must survive process restart.
- Store status on the edge or relationship when the child thread's own status is not enough.
Known trap:
- Reconstructing topology from message text works until resumes, compaction, or remote tasks split the transcript.
## Portable Runtime Contract
For a coding-agent task system, import these fields:
```text
TaskRequest
prompt
repo/workspace
branch
environment
attempt_count
parent_thread_id?
requested_by
TaskAttempt
attempt_id
task_id
status
transcript_ref
diff_ref?
result_ref?
TaskApplyResult
task_id
attempt_id
result_level: success | partial | error
applied_paths[]
skipped_paths[]
conflict_paths[]
```
## Tests To Require
- Creating a task with invalid attempt count is rejected before dispatch.
- Listing tasks cannot cross the requested environment.
- Diff does not mutate local files.
- Apply reports partial success when one path applies and one conflicts.
- Applying the same attempt twice is idempotent or explicitly refused.
- Parent-child graph returns stable child ordering.
- Descendant traversal does not loop if bad data creates a cycle.
## Source Links
- [cloud-tasks CLI](https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/cloud-tasks/src/cli.rs)
- [cloud-tasks app](https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/cloud-tasks/src/app.rs)
- [agent graph store](https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/agent-graph-store/src/store.rs)
references/task-list-coordination-and-teammate-routing.md
# Task List Coordination And Teammate Routing
## Table Of Contents
- [Design Goal](#design-goal)
- [File-Watched Task Lists](#file-watched-task-lists)
- [Claiming And Release](#claiming-and-release)
- [Blocked Tasks](#blocked-tasks)
- [Teammate Routing](#teammate-routing)
## Design Goal
Background task systems for coding agents should support both host-owned runtime tasks and externally-created task lists that workers can pick up automatically.
## File-Watched Task Lists
`useTaskListWatcher.ts` shows a practical model:
- watch a task-list directory
- debounce filesystem events
- check for work only when the agent is idle
- submit one claimed task as the next prompt
This creates a durable “tasks mode” without requiring continuous polling from the main session loop.
## Claiming And Release
The watcher also shows correct claim behavior:
- claim a task before turning it into work
- if submission fails, release the claim
- keep current-task tracking so the worker does not pick up new work until the old one is resolved
That is the right baseline for multi-worker task acquisition.
## Blocked Tasks
The available-task filter is not just “first pending task.” It also checks:
- no owner assigned
- blockers are completed
This is a good minimum scheduler for coding-agent task queues with dependencies.
## Teammate Routing
The teammate navigation and swarm-permission code show that worker tasks are not just queue items. They also participate in leader-worker coordination:
- teammate tasks have dedicated views
- leader and teammate views are distinct
- permission responses may route back from the leader to a worker task
Treat teammate tasks as runtime actors with UI and approval implications, not just rows in a task list.
references/task-types-and-lifecycle.md
# Task Types And Lifecycle
## Table Of Contents
- [Design Goal](#design-goal)
- [Typed Task Families](#typed-task-families)
- [Background Visibility](#background-visibility)
- [Cancellation Semantics](#cancellation-semantics)
## Design Goal
Coding-agent runtimes need a typed task model. The `claude_code` runtime distinguishes task families instead of treating all work as one generic async job.
**Naming note (verified 2026-07-11):** Claude Code renamed the subagent-spawn tool from `Task` to `Agent` in v2.1.63. Legacy `Task(...)` references still resolve as an alias. Do not read "Task" and "Agent" in a snapshot or a user's transcript as two different task families — check the version and changelog before concluding a runtime has two separate subagent primitives.
## Typed Task Families
`tasks/types.ts` and the task modules show a union of concrete task families such as:
- local shell tasks
- local agent tasks
- remote agent tasks
- in-process teammate tasks
- workflow tasks
- monitor tasks
- dream or speculative tasks
This is the right pattern:
- type task families explicitly
- give each one its own lifecycle and detail renderer
- use a shared union only where the UI or host truly needs “any task”
## Background Visibility
`isBackgroundTask(...)` in `tasks/types.ts` encodes an important rule:
- only running or pending work counts
- foregrounded work should not be shown as background work
That is worth copying because coding-agent UIs often over-count tasks and confuse the user about what is actually “in the background.”
## Cancellation Semantics
The surrounding task and teammate code distinguishes:
- abort current work
- keep the worker alive
- kill the task or worker entirely
Preserve that distinction. “Interrupt” and “kill” are not the same lifecycle action in coding-agent runtimes.
references/webhook-and-queue-triggers.md
# Webhook and Queue Triggers
Use this reference when the agent is **Shape A — Hosted / event-triggered**: a fresh agent session is spawned by an external event (webhook delivery, queue message, schedule firing) and runs to completion. The trigger substrate lives outside the agent runtime.
May 2026 baseline: every serious autonomy deployment routes through an idempotent trigger layer. Naked HTTP-to-agent endpoints fail under retries, duplicate webhooks, and cron drift.
## Table of Contents
- [Trigger Topology](#trigger-topology)
- [Trigger Sources Compared (May 2026)](#trigger-sources-compared-may-2026)
- [Idempotency Contract](#idempotency-contract)
- [Deduplication Window](#deduplication-window)
- [Webhook Triggers](#webhook-triggers)
- [Queue Triggers (SQS / Redis Streams / Kafka)](#queue-triggers-sqs--redis-streams--kafka)
- [Schedule Triggers](#schedule-triggers)
- [Dead Letter Queues](#dead-letter-queues)
- [Backpressure and Concurrency](#backpressure-and-concurrency)
- [Trigger Payload Schema](#trigger-payload-schema)
- [Approval Gates Inside the Trigger](#approval-gates-inside-the-trigger)
- [Observability for Triggers](#observability-for-triggers)
- [Common Failure Modes](#common-failure-modes)
- [Cross-References](#cross-references)
## Trigger Topology
```text
External event Trigger substrate Agent runtime
┌──────────────┐ HTTP ┌────────────────────────┐ ┌─────────────────┐
│ Webhook │ ────────▶ │ Idempotency layer │ │ Agent session │
│ (Stripe, │ │ (key → payload hash) │ ────▶ │ (fresh ctx) │
│ GitHub, │ │ └─ dedup window │ │ │
│ etc.) │ └──────────┬─────────────┘ │ Loads PRD, │
└──────────────┘ │ │ skills, MCP │
▼ │ servers │
┌──────────────┐ put ┌────────────────────────┐ │ │
│ Producer │ ───────▶ │ Queue (SQS/Streams/ │ │ Reports result │
└──────────────┘ │ Kafka) │ ────▶ │ │
│ └─ visibility timeout │ └────────┬────────┘
└────────────────────────┘ │
▼
┌──────────────┐ cron ┌────────────────────────┐ ┌─────────────────┐
│ Scheduler │ ───────▶ │ Schedule firing │ ────▶ │ Result sink │
│ (cron/EB/ │ │ (with jitter) │ │ (DB, Slack, │
│ /fire) │ └────────────────────────┘ │ next queue) │
└──────────────┘ └─────────────────┘
```
Three substrates, one universal trigger contract: every event must carry an idempotency key, schema-validated payload, and replay-safe semantics.
## Trigger Sources Compared (May 2026)
| Source | Delivery | Native Idempotency | Best For | Caution |
|---|---|---|---|---|
| **Anthropic `/fire` + schedule** | At-most-once | No (build yourself) | Schedule-driven agent runs inside Claude Code | Beta header; cap-drop behavior |
| **OpenAI scheduled tasks** | At-most-once | No | Codex scheduled runs | GA but trigger payload is opaque |
| **AWS EventBridge → Lambda → agent** | At-least-once | EventBridge idempotency token | Production-grade scheduling | Lambda 15-min cap forces long agent runs into Step Functions or Temporal |
| **AWS Bedrock AgentCore** | At-most-once for schedules, at-least-once for events | Yes (action-group level) | AWS-native agent stacks | Vendor-locked; payload schema is Bedrock-specific |
| **GitHub Actions (workflow_dispatch / webhooks)** | At-least-once | No | CI-triggered agent runs, PR review agents | Concurrency limits per repo |
| **Webhook gateway (Svix, Hookdeck)** | At-least-once with retry | Yes (idempotency key passthrough) | Multi-source webhook fan-in | Cost scales with volume |
| **SQS standard** | At-least-once | No | High-throughput async work | Duplicates expected; dedup layer required |
| **SQS FIFO** | Exactly-once within 5-min window | Yes (MessageDeduplicationId) | Order-sensitive agent runs | Lower throughput than standard |
| **Redis Streams** | At-least-once | No | Low-latency, single-region | Consumer-group offset tracking needed |
| **Kafka** | At-least-once (configurable) | No | High-volume multi-consumer | Operational complexity high |
| **Inngest / Trigger.dev / Temporal** | Exactly-once (durable) | Yes | Multi-step agent workflows | Covered in [`durable-trigger-integration.md`](durable-trigger-integration.md) |
Choose by failure tolerance, not by familiarity. Most agent products land on either Temporal/Inngest (for orchestration) or SQS+Lambda (for fan-out), with webhooks gated through Svix or Hookdeck.
## Idempotency Contract
Every trigger event has an **idempotency key**. The agent runtime stores `(key → first_result, expires_at)` in a fast KV (Redis, DynamoDB). On duplicate delivery, the stored result is returned without invoking the agent.
```python
import redis, hashlib, json
from datetime import timedelta
r = redis.Redis()
IDEMP_TTL = timedelta(hours=24)
def idempotency_key(event: dict) -> str:
# Prefer the source's native key; fall back to payload hash.
return event.get("idempotency_key") or hashlib.sha256(
json.dumps(event, sort_keys=True).encode()
).hexdigest()
def try_acquire(key: str) -> tuple[bool, dict | None]:
pipe = r.pipeline()
pipe.set(f"idemp:{key}:lock", "1", nx=True, ex=int(IDEMP_TTL.total_seconds()))
pipe.get(f"idemp:{key}:result")
acquired, prior = pipe.execute()
if not acquired and prior:
return False, json.loads(prior)
return True, None
def record_result(key: str, result: dict) -> None:
r.set(f"idemp:{key}:result", json.dumps(result), ex=int(IDEMP_TTL.total_seconds()))
```
Rules:
1. The agent invocation is wrapped in `try_acquire` / `record_result`.
2. The key TTL must exceed the maximum upstream retry window (24h is safe for most webhook providers).
3. If `try_acquire` returns `(False, None)` the request is in flight — return 409 and let the upstream retry later.
4. The result stored must be deterministic enough to replay (typically: the structured response payload, not the full agent transcript).
## Deduplication Window
Idempotency keys catch exact replays. Dedup windows catch near-duplicates: same logical event, different timestamps or trace IDs.
Pattern: derive a content-based dedup key (e.g., `(user_id, action_type, target_id)`) and reject anything seen in the last N minutes.
```python
def is_duplicate(dedup_key: str, window_seconds: int = 300) -> bool:
return r.set(f"dedup:{dedup_key}", "1", nx=True, ex=window_seconds) is None
```
Use 5 minutes for user-action triggers, 1 hour for periodic recompute triggers, 24h for cron-driven catch-up runs.
## Webhook Triggers
Stripe, GitHub, Linear, Slack, Intercom — all webhook providers retry on non-2xx. Build for retries from day one.
Minimum handler:
```python
from fastapi import FastAPI, Request, Header, HTTPException
import hmac, hashlib
app = FastAPI()
WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"]
def verify_signature(body: bytes, signature: str) -> bool:
expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
@app.post("/webhooks/stripe")
async def stripe_webhook(request: Request, stripe_signature: str = Header(...)):
body = await request.body()
if not verify_signature(body, stripe_signature):
raise HTTPException(401, "bad signature")
event = json.loads(body)
key = event["id"] # Stripe sends a unique event id
acquired, prior = try_acquire(key)
if not acquired:
return prior or {"status": "in_flight"}
# Hand off to agent
result = await invoke_agent(event)
record_result(key, result)
return result
```
Critical: **always return 2xx within the provider's timeout** (Stripe: 30s, GitHub: 10s, most others: <10s). If the agent run is longer, enqueue to a worker queue and return immediately.
```python
@app.post("/webhooks/stripe")
async def stripe_webhook(request: Request, stripe_signature: str = Header(...)):
body = await request.body()
if not verify_signature(body, stripe_signature):
raise HTTPException(401, "bad signature")
event = json.loads(body)
await sqs.send_message(QueueUrl=QUEUE, MessageBody=json.dumps(event),
MessageDeduplicationId=event["id"],
MessageGroupId=event["account"])
return {"status": "queued"}
```
## Queue Triggers (SQS / Redis Streams / Kafka)
SQS-driven Lambda is the path of least resistance for agent fan-out. Up to 1000 concurrent agent invocations, automatic retries, native DLQ.
```python
# Lambda handler
def handler(event, context):
for record in event["Records"]:
msg = json.loads(record["body"])
key = msg.get("idempotency_key") or record["messageId"]
acquired, prior = try_acquire(key)
if not acquired:
continue
try:
result = run_agent_sync(msg)
record_result(key, result)
except RetryableError as e:
raise # SQS will retry; release the lock first
except FatalError as e:
send_to_dlq(record, str(e))
```
Visibility timeout = max agent run time + buffer. If agents take 5 minutes, set visibility timeout to 6 minutes minimum. If they exceed, SQS will redeliver and you'll run twice — idempotency key catches this, but it costs you tokens.
**Kafka pattern** (for very high volume):
```python
from confluent_kafka import Consumer
consumer = Consumer({
"bootstrap.servers": "...",
"group.id": "agent-workers",
"enable.auto.commit": False, # commit only after successful agent run
})
consumer.subscribe(["agent-triggers"])
while True:
msg = consumer.poll(timeout=1.0)
if msg is None or msg.error():
continue
event = json.loads(msg.value())
key = event["idempotency_key"]
acquired, prior = try_acquire(key)
if acquired:
try:
result = run_agent_sync(event)
record_result(key, result)
consumer.commit(msg)
except Exception:
# Do not commit — Kafka redelivers on next poll
log_failure(event)
else:
consumer.commit(msg) # already handled, advance offset
```
## Schedule Triggers
Use schedules for: periodic reconciliation, daily digests, hourly polling of external systems, time-of-day announcements.
| Substrate | Cron support | Drift tolerance | Notes |
|---|---|---|---|
| Anthropic `/fire` schedules | Yes | ~minute | Beta; see [`claude-code-routines.md`](claude-code-routines.md) |
| AWS EventBridge Scheduler | Yes, timezone-aware | <1 second | Default for AWS stacks |
| GCP Cloud Scheduler | Yes | <1 second | Default for GCP stacks |
| Temporal cron workflows | Yes | <1 second | Best for multi-step scheduled work |
| GitHub Actions `schedule:` | Yes (UTC only) | 5–30 min (often delayed) | Free but unreliable for precise timing |
**Always add jitter** to schedules that hit shared resources:
```python
import random
def cron_with_jitter(base_cron: str, max_jitter_seconds: int = 60):
return f"sleep {random.randint(0, max_jitter_seconds)} && {base_cron}"
```
Without jitter, 100 agents on the same `0 * * * *` schedule will hammer the LLM provider at the same instant.
## Dead Letter Queues
A DLQ is mandatory. Any agent run that fails N times (default 3) must land in the DLQ with full context so a human can triage.
DLQ payload schema:
```json
{
"original_event": { ... },
"attempts": 3,
"errors": [
{"attempt": 1, "ts": "2026-05-20T10:00:00Z", "error": "..."},
{"attempt": 2, "ts": "2026-05-20T10:01:00Z", "error": "..."},
{"attempt": 3, "ts": "2026-05-20T10:02:00Z", "error": "..."}
],
"first_seen": "2026-05-20T10:00:00Z",
"triage_url": "https://..."
}
```
DLQ items must alert. A DLQ no one watches is a silent failure mode (Coding Behavior Rule 12).
## Backpressure and Concurrency
LLM providers have per-minute and per-day rate limits. The trigger layer is the right place to enforce them, not the agent.
```python
from redis import Redis
r = Redis()
def acquire_provider_slot(provider: str, max_concurrent: int) -> bool:
current = r.incr(f"slots:{provider}")
if current > max_concurrent:
r.decr(f"slots:{provider}")
return False
return True
def release_provider_slot(provider: str) -> None:
r.decr(f"slots:{provider}")
```
If acquisition fails, requeue with exponential backoff. Do **not** burst into 429s — the cooldown periods kill throughput more than throttled-but-paced traffic.
Provider tier limits (May 2026, indicative):
| Provider | Tier | Concurrent | TPM (input) |
|---|---|---|---|
| Anthropic Tier 4 | Production | ~50–100 | 400k |
| OpenAI Tier 5 | Production | ~500 | 2M |
| AWS Bedrock | Region-dependent | Quota-managed | Region-dependent |
Always check live quotas before sizing concurrency.
## Trigger Payload Schema
Every trigger payload must conform to a versioned schema. Validate before invoking the agent. Garbage-in produces garbage-out at LLM prices.
```python
from pydantic import BaseModel, Field
from typing import Literal
class AgentTriggerV1(BaseModel):
schema_version: Literal["v1"]
idempotency_key: str
source: str # "webhook:stripe", "queue:sqs:invoices", "schedule:daily-recon"
occurred_at: str # ISO 8601
payload: dict # source-specific
metadata: dict = Field(default_factory=dict)
approval_required: bool = False
cost_budget_usd: float | None = None
max_duration_seconds: int = 300
```
Validation failures route to a separate DLQ ("malformed input") so they don't contaminate retry counts.
## Approval Gates Inside the Trigger
For high-risk actions, the trigger does not invoke the agent directly. It creates an approval task and waits.
```python
async def trigger_with_approval(event: AgentTriggerV1) -> dict:
if event.approval_required or is_high_risk(event):
task_id = await create_approval_task(event)
await notify_approvers(task_id)
return {"status": "pending_approval", "task_id": task_id}
return await invoke_agent(event)
```
Approval flows belong here, not inside the agent, because: (1) approvers cannot review what the agent has already done; (2) the trigger layer is auditable in a way the agent's reasoning is not.
## Observability for Triggers
Per-event signals:
- `trigger.received` — source, schema_version, payload_size
- `trigger.deduplicated` — when idempotency or dedup kicked in
- `trigger.queued` — when handed to async worker
- `trigger.agent_invoked` — start of agent run
- `trigger.completed` — duration, tokens, cost, result_size
- `trigger.failed` — error class, attempt number
- `trigger.dlq` — when an item lands in DLQ
Per-source metrics:
- arrival rate
- p50 / p99 end-to-end latency (trigger receipt → agent completion)
- duplicate rate
- DLQ rate
- approval rate (for high-risk sources)
Dashboard rule: a healthy trigger system has dedup-rate < 5%, DLQ-rate < 1%, and p99 latency within SLO.
## Common Failure Modes
| Failure | Symptom | Mitigation |
|---|---|---|
| **Duplicate agent runs** | Same work done twice, double-billing | Idempotency key + dedup window |
| **Webhook timeout** | Provider retries, agent runs twice | Return 2xx fast; enqueue async |
| **Visibility timeout < agent runtime** | SQS redelivers mid-run | Set timeout = max_runtime + 20% |
| **Bursty schedule fan-out** | LLM provider 429s | Cron jitter + provider slot accounting |
| **Silent DLQ** | Failures pile up uninspected | Alert on DLQ depth > 0 |
| **Schema drift** | Trigger fields change, agent breaks | Versioned schema + reject unknown versions |
| **Missing signature verification** | Spoofed webhook drains budget | Always verify HMAC before any work |
| **Synchronous webhook → long agent** | Provider disables the webhook endpoint | Decouple via queue |
## Cross-References
- [`durable-trigger-integration.md`](durable-trigger-integration.md) — when the trigger needs Temporal/Inngest semantics
- [`claude-code-routines.md`](claude-code-routines.md) — Anthropic-hosted schedule and `/fire` specifics
- [`task-types-and-lifecycle.md`](task-types-and-lifecycle.md) — task model the trigger feeds into
- [`../../ai-agents/references/autonomous-loop-patterns.md`](../../ai-agents/references/autonomous-loop-patterns.md) — Shape C consumes Shape A triggers
- [`../../ai-agents/references/24-7-operating-model.md`](../../ai-agents/references/24-7-operating-model.md) — SLOs, oncall, runbooks
- [`../../agents-hooks/references/budget-and-loop-hooks.md`](../../agents-hooks/references/budget-and-loop-hooks.md) — enforcing budgets per trigger
- [`../../software-workflow-automation/SKILL.md`](../../software-workflow-automation/SKILL.md) — workflow substrates
- [`../../ai-mlops/references/incident-response-playbooks.md`](../../ai-mlops/references/incident-response-playbooks.md) — incident response for trigger systems
scripts/recipe_scanner.py
"""
recipe_scanner.py
Static validator for Goose-style recipe blueprint YAML files.
Python stdlib only — no external dependencies required.
A recipe blueprint must pass all checks before it can be promoted to a
typed task blueprint in the agent runtime. This validator is the
reference implementation of the "recipe-scanner" check mentioned in
SKILL.md.
Usage:
python recipe_scanner.py recipe.yaml # validate one file
python recipe_scanner.py recipes/ # validate all .yaml in dir
python recipe_scanner.py --strict recipe.yaml # treat warnings as errors
Exit codes:
0 — all files passed (or only warnings in non-strict mode)
1 — one or more errors found
"""
from __future__ import annotations
import argparse
import sys
import re
from pathlib import Path
from typing import Any
# ---------------------------------------------------------------------------
# Minimal YAML parser (stdlib only)
# ---------------------------------------------------------------------------
def _parse_yaml_simple(text: str) -> dict:
"""
Extremely minimal YAML parser that handles the flat and one-level-nested
structures used in recipe blueprints. Supports:
- top-level key: value pairs
- top-level key: followed by indented list items (- item)
- top-level key: followed by indented key: value pairs
- multi-line values using | (block literal) — stored as raw string
- inline lists: [a, b, c]
- quoted strings: "..." and '...'
- comments (#)
Not a general YAML parser. For production use, add PyYAML.
"""
result: dict[str, Any] = {}
lines = text.splitlines()
i = 0
def strip_comment(s: str) -> str:
# Remove inline comments (outside quotes)
in_quote: str | None = None
for idx, ch in enumerate(s):
if in_quote:
if ch == in_quote:
in_quote = None
elif ch in ('"', "'"):
in_quote = ch
elif ch == "#":
return s[:idx].rstrip()
return s
def unquote(s: str) -> str:
s = s.strip()
if (s.startswith('"') and s.endswith('"')) or (
s.startswith("'") and s.endswith("'")
):
return s[1:-1]
return s
while i < len(lines):
line = lines[i]
stripped = strip_comment(line)
if not stripped.strip() or stripped.strip().startswith("#"):
i += 1
continue
# Top-level key
m = re.match(r'^([A-Za-z_][A-Za-z0-9_\-]*):\s*(.*)', stripped)
if not m:
i += 1
continue
key = m.group(1)
rest = m.group(2).strip()
# Inline list: key: [a, b, c]
if rest.startswith("[") and rest.endswith("]"):
items = [unquote(x) for x in rest[1:-1].split(",") if x.strip()]
result[key] = items
i += 1
continue
# Block literal: key: |
if rest == "|":
block_lines = []
i += 1
while i < len(lines):
bl = lines[i]
if bl and not bl[0].isspace():
break
block_lines.append(bl.rstrip())
i += 1
result[key] = "\n".join(block_lines)
continue
# Scalar value on same line
if rest:
result[key] = unquote(rest)
i += 1
continue
# Multi-line: collect indented children
children_lines = []
i += 1
while i < len(lines):
cl = lines[i]
if cl and not cl[0].isspace():
break
children_lines.append(cl)
i += 1
# Determine if children are a list or a mapping
is_list = any(re.match(r'^\s+-\s+', c) for c in children_lines if c.strip())
if is_list:
items = []
for cl in children_lines:
m2 = re.match(r'^\s+-\s+(.*)', cl)
if m2:
items.append(unquote(m2.group(1).strip()))
result[key] = items
else:
sub: dict[str, str] = {}
for cl in children_lines:
sm = re.match(r'^\s+([A-Za-z_][A-Za-z0-9_\-]*):\s*(.*)', cl)
if sm:
sub[sm.group(1)] = unquote(sm.group(2).strip())
if sub:
result[key] = sub
return result
# ---------------------------------------------------------------------------
# Validation checks
# ---------------------------------------------------------------------------
REQUIRED_FIELDS = ["version", "title", "description", "instructions"]
OPTIONAL_FIELDS = ["author", "extensions", "activities", "prompt", "parameters"]
KNOWN_FIELDS = set(REQUIRED_FIELDS + OPTIONAL_FIELDS)
# Parameters are declared as a list of objects with these sub-keys
PARAM_REQUIRED_KEYS = {"key", "input_type", "requirement", "description"}
PARAM_OPTIONAL_KEYS = {"default"}
VALID_INPUT_TYPES = {"string", "boolean", "integer", "float", "list"}
VALID_REQUIREMENTS = {"required", "optional"}
# Security gate: extensions that touch the network or filesystem
HIGH_RISK_EXTENSIONS = {
"network", "web_search", "filesystem_write", "shell", "bash",
"code_execution", "docker", "container", "mcp__github", "mcp__slack",
}
def _check(errors: list, warnings: list, condition: bool, message: str, is_error: bool = True) -> None:
if not condition:
(errors if is_error else warnings).append(message)
def validate_recipe(path: Path, strict: bool = False) -> tuple[list[str], list[str]]:
"""
Returns (errors, warnings). errors is non-empty if the recipe is invalid.
warnings are non-fatal unless strict=True.
"""
errors: list[str] = []
warnings: list[str] = []
# --- Read file ---
try:
text = path.read_text(encoding="utf-8")
except OSError as e:
errors.append(f"Cannot read file: {e}")
return errors, warnings
# --- Parse ---
try:
recipe = _parse_yaml_simple(text)
except Exception as e: # noqa: BLE001
errors.append(f"YAML parse error: {e}")
return errors, warnings
if not recipe:
errors.append("File parsed to empty dict — check YAML syntax")
return errors, warnings
# --- Required fields ---
for field in REQUIRED_FIELDS:
if field not in recipe or not recipe[field]:
errors.append(f"Missing required field: '{field}'")
# --- Version format (must be semver-like: N.N or N.N.N) ---
version = recipe.get("version", "")
if version and not re.match(r'^\d+\.\d+(\.\d+)?$', str(version)):
errors.append(f"'version' must be semver-like (e.g. 1.0 or 1.0.0), got: '{version}'")
# --- Title length ---
title = recipe.get("title", "")
if title and len(str(title)) > 120:
warnings.append(f"'title' is very long ({len(str(title))} chars); keep under 120")
# --- Instructions non-trivial ---
instructions = recipe.get("instructions", "")
if instructions and len(str(instructions).split()) < 5:
warnings.append("'instructions' appears too short; verify it is not a placeholder")
# --- Unknown top-level fields ---
for key in recipe:
if key not in KNOWN_FIELDS:
warnings.append(f"Unknown top-level field: '{key}' (known: {sorted(KNOWN_FIELDS)})")
# --- Parameters validation ---
params = recipe.get("parameters")
if params is not None:
if not isinstance(params, list):
errors.append("'parameters' must be a list of parameter objects")
else:
for idx, param in enumerate(params):
if not isinstance(param, dict):
errors.append(f"parameters[{idx}] must be a mapping, got: {type(param).__name__}")
continue
for req_key in PARAM_REQUIRED_KEYS:
if req_key not in param or not param[req_key]:
errors.append(f"parameters[{idx}] missing required sub-key: '{req_key}'")
input_type = param.get("input_type", "")
if input_type and input_type not in VALID_INPUT_TYPES:
errors.append(
f"parameters[{idx}].input_type '{input_type}' is not valid "
f"(valid: {sorted(VALID_INPUT_TYPES)})"
)
requirement = param.get("requirement", "")
if requirement and requirement not in VALID_REQUIREMENTS:
errors.append(
f"parameters[{idx}].requirement '{requirement}' is not valid "
f"(valid: {sorted(VALID_REQUIREMENTS)})"
)
unknown_param_keys = set(param.keys()) - PARAM_REQUIRED_KEYS - PARAM_OPTIONAL_KEYS
for uk in sorted(unknown_param_keys):
warnings.append(f"parameters[{idx}] has unknown sub-key: '{uk}'")
# --- Extensions validation ---
extensions = recipe.get("extensions")
if extensions is not None:
if not isinstance(extensions, list):
errors.append("'extensions' must be a list of extension names or references")
else:
for ext in extensions:
ext_lower = str(ext).lower()
for risky in HIGH_RISK_EXTENSIONS:
if risky in ext_lower:
warnings.append(
f"Extension '{ext}' looks high-risk (matches '{risky}'). "
"Verify this recipe is allowed to use network/filesystem/shell tools."
)
break
# --- Activities validation ---
activities = recipe.get("activities")
if activities is not None and not isinstance(activities, list):
errors.append("'activities' must be a list")
# --- Unresolved placeholders ---
placeholder_re = re.compile(r'\{\{[^}]+\}\}')
for field_name in REQUIRED_FIELDS + OPTIONAL_FIELDS:
value = recipe.get(field_name, "")
if isinstance(value, str) and placeholder_re.search(value):
warnings.append(f"Field '{field_name}' contains unresolved placeholder: {placeholder_re.findall(value)}")
return errors, warnings
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def _collect_paths(target: str) -> list[Path]:
p = Path(target)
if p.is_dir():
return sorted(p.glob("**/*.yaml")) + sorted(p.glob("**/*.yml"))
return [p]
def main() -> int:
parser = argparse.ArgumentParser(
description="Static validator for recipe blueprint YAML files (stdlib only)."
)
parser.add_argument("targets", nargs="+", help="Recipe file(s) or directory")
parser.add_argument(
"--strict",
action="store_true",
help="Treat warnings as errors",
)
args = parser.parse_args()
paths: list[Path] = []
for t in args.targets:
paths.extend(_collect_paths(t))
if not paths:
print("No .yaml or .yml files found.", file=sys.stderr)
return 1
total_errors = 0
total_warnings = 0
for path in paths:
errors, warnings = validate_recipe(path, strict=args.strict)
if args.strict:
errors = errors + warnings
warnings = []
if errors or warnings:
print(f"\n{'[FAIL]' if errors else '[WARN]'} {path}")
for e in errors:
print(f" ERROR: {e}")
for w in warnings:
print(f" WARN: {w}")
else:
print(f" [OK] {path}")
total_errors += len(errors)
total_warnings += len(warnings)
print(
f"\nSummary: {len(paths)} file(s) — "
f"{total_errors} error(s), {total_warnings} warning(s)"
)
return 0 if total_errors == 0 else 1
if __name__ == "__main__":
sys.exit(main())
SKILL.md
---
name: ai-coding-agents-tasks
description: "Designs task runtimes for Loop Engineering, Graph Engineering, and background work. Use when work needs task lists, cyclic/workflow graphs, cancellation, or teammate coordination."
compatibility: Portable core. Works on Claude Code and Codex.
version: "1.2"
last_validated: 2026-08-11
---
# AI Coding Agents Tasks
Use this skill to design or review how a coding-agent runtime represents work as tasks: local agent tasks, shell tasks, remote tasks, teammate tasks, background execution, task lists, and task coordination.
This skill is about runtime task systems, not product backlog planning.
## ASCII Flow
```text
work request
|
v
task model
type + owner + status + inputs + blockers + cancellation semantics
|
v
task store
local task list | background task | remote task | teammate task
|
v
scheduling
claim -> run -> progress events -> complete | fail | blocked | release
|
v
UI/session reflection
background badge, task detail, foregrounding, retry, escalation
```
## Quick Reference
| Question | Read | Outcome |
|----------|------|---------|
| What task types should a coding-agent runtime support? | [`references/task-types-and-lifecycle.md`](references/task-types-and-lifecycle.md) | Task families, statuses, background eligibility, and host ownership |
| How should task lists and teammate routing work? | [`references/task-list-coordination-and-teammate-routing.md`](references/task-list-coordination-and-teammate-routing.md) | File-watched task lists, claiming, blockers, teammate routing, and response loops |
| How do Anthropic-hosted routines (schedule / API / GitHub) behave? | [`references/claude-code-routines.md`](references/claude-code-routines.md) | Trigger types, `/fire` endpoint, beta header, cap-drop behaviour, fresh-session model, comparison with Actions / n8n / cron |
| How do I trigger agents from webhooks, queues, or schedules at scale? | [`references/webhook-and-queue-triggers.md`](references/webhook-and-queue-triggers.md) | Shape A — SQS / Streams / Kafka / EventBridge, idempotency, dedup, DLQ, provider rate-limit slots |
| How do I make agent runs durable across crashes and multi-step? | [`references/durable-trigger-integration.md`](references/durable-trigger-integration.md) | Temporal / Inngest / Restate / Step Functions, agent-as-activity, sagas, signals, replay |
| Where do I actually host the trigger + agent (Vercel, Fly, CF, Render)? | [`../software-paas-hosting/references/agent-hosting-matrix.md`](../software-paas-hosting/references/agent-hosting-matrix.md) | Per-shape PaaS stacks + reference architectures |
| How do I statically validate a recipe blueprint YAML? | [`scripts/recipe_scanner.py`](scripts/recipe_scanner.py) | stdlib-only validator: required fields, parameter types, extension risk gates, placeholder detection |
| How does OpenAI Codex model cloud tasks, apply/diff, and agent graphs? | [`references/openai-codex-cloud-tasks-and-agent-graph.md`](references/openai-codex-cloud-tasks-and-agent-graph.md) | Task CLI lifecycle, best-of-N attempts, partial apply states, environment filters, and persisted parent-child topology |
| Where is the runtime surface for Loop Engineering and Graph Engineering — including cyclic/workflow graphs? | [`references/loop-and-graph-runtime-surfaces.md`](references/loop-and-graph-runtime-surfaces.md) | Surface matrix, queue-vs-cyclic-graph data model, portable loop contract, scheduled-loop state rules |
## When To Use
- Design background work for a coding-agent CLI or runtime
- Add task lists, worker tasks, or background execution surfaces
- Decide how local, remote, and teammate tasks should differ
- Model cancellation, claiming, blocking, and foreground/background transitions
- Coordinate work between a lead session and worker tasks
- Implement Loop Engineering or Graph Engineering runtime surfaces for cyclic or workflow graphs
## Use Other Skills
| Need | Use Instead |
|------|-------------|
| Multi-agent planning and ownership contracts | [`../agents-swarm-orchestration/SKILL.md`](../agents-swarm-orchestration/SKILL.md) |
| Loop shapes, termination predicates, and convergence detection | [`../agents-swarm-orchestration/references/loop-orchestration.md`](../agents-swarm-orchestration/references/loop-orchestration.md) |
| Script-held deterministic control flow (Claude Code Workflows) | [`../agents-swarm-orchestration/references/scripted-workflows.md`](../agents-swarm-orchestration/references/scripted-workflows.md) |
| Terminal UI and task dialogs | [`../ai-coding-agents-terminal-ui/SKILL.md`](../ai-coding-agents-terminal-ui/SKILL.md) |
| Session resume and transcript persistence | [`../ai-coding-agents-sessions/SKILL.md`](../ai-coding-agents-sessions/SKILL.md) |
| Prompt-cache economics when spawning subagent tasks | [`../ai-coding-agents-sessions/references/resume-path-decision-tree.md`](../ai-coding-agents-sessions/references/resume-path-decision-tree.md) — blank vs. forked subagent startup, cache-prefix economics; also [`../ai-coding-agents-sessions/references/context-forking.md`](../ai-coding-agents-sessions/references/context-forking.md) |
| Tool-call permission gating, approval modes, and policy inheritance for subagents | [`../ai-coding-agents-permissions/SKILL.md`](../ai-coding-agents-permissions/SKILL.md) |
## Default Workflow
1. **Model task types explicitly.** Local shell, local agent, remote agent, teammate, workflow, and monitor-style tasks should not be one untyped blob.
2. **Keep task state in the host store.** Status, ownership, backgrounding, and timestamps belong to runtime state, not just UI widgets.
3. **Define background eligibility.** Only running or pending tasks that are actually backgrounded should appear in background-task surfaces.
4. **Use claiming for shared task lists.** External or teammate task lists need explicit claim and release behavior.
5. **Respect blockers and ownership.** Pending tasks with unresolved blockers or owners are not available for automatic pickup.
6. **Differentiate abort from kill.** Interrupting current work is not the same as terminating the worker or task object.
7. **Stabilize task-list identity.** Task list IDs should resolve from explicit context first and use monotonic high-water-mark rules so resets do not accidentally reuse old IDs.
8. **Serialize shared mutations.** Host-owned task stores need lock or backoff semantics when multiple workers can touch the same list.
9. **Test concurrency edges.** Verify double claim, failed submission after claim, blocked task ordering, background/foreground transitions, and remote-task cancellation.
## Host Rules
- Task type and task state should be typed separately from UI rendering.
- Background task indicators should only show truly backgrounded running or pending work.
- Claiming must be atomic enough to avoid duplicate workers taking the same task.
- Lead and teammate navigation should stay separate from generic task-list execution.
- Cancellation semantics must distinguish “stop this turn” from “kill this task.”
- Task-list identity should be host-owned and sanitized before it becomes a filesystem or external synchronization key.
## Build Order
1. Define typed task families and lifecycle states.
2. Put task state in a host-owned store with timestamps and ownership.
3. Add background eligibility and foreground transition rules.
4. Add claiming and release semantics for shared task lists.
5. Add task-list identity resolution and collision-resistant non-reuse rules.
6. Add blocker resolution, retry logic, and serialized shared mutations.
7. Add teammate and remote-task routing with explicit cancellation behavior.
## Core Invariants
- Task type and lifecycle state are runtime data, not UI decoration.
- Claiming shared work must be explicit and collision-resistant.
- Background visibility should reflect real background execution, not intent.
- Abort and kill are different actions with different guarantees.
- Lead-session navigation and teammate-task routing are not generic queue operations.
- Task-list IDs must not be reused casually after reset or crash.
## Failure Modes
- Multiple workers claiming the same task.
- Submission failing after claim without releasing ownership.
- Blocked tasks being scheduled as if they were runnable.
- Background indicators showing tasks that are not actually executing.
- Cancellation stopping the current turn but leaving the task object running indefinitely.
- Task-list resets reusing old IDs and colliding with stale watcher or worker state.
- A background task silently auto-denying an unapproved tool call and stalling in a read-only loop instead of surfacing the pending-permission state to the owning session (see [Background-Subagent Task Semantics](#background-subagent-task-semantics-claude-code-2026-0607)).
## Minimal Viable Version
- One typed task model with statuses and ownership.
- One host-owned task store.
- One background-task filter based on real state.
- One atomic-enough claim and release path.
- One host-owned task-list ID strategy that survives restart and reset.
- One clear distinction between aborting work and killing a task.
## What Strong Implementations Add
- File-watched or externally synchronized task lists.
- Teammate routing and lead-session coordination flows.
- Debounced watcher updates and stable callbacks.
- Lockfile or backoff discipline for concurrent task-list mutation.
- Explicit blocker resolution, retry, and escalation transitions.
- Remote-task and workflow-task families beyond local shell or local agent tasks.
- **Typed recipe blueprints** with declared parameters, pinned extensions, and static validation at load time.
- **Capability-narrowed subagents** whose extension envelope is a strict subset of the lead's.
- **Sub-recipe composition** as a distinct task relationship, with cascading cancellation and blocker semantics.
## Known Traps
- Treating tasks as chat affordances only and never defining the runtime lifecycle, owner, blocker, or cancellation semantics behind them.
- Assuming claim operations are atomic without any collision handling, lock discipline, or stale-owner recovery.
- Letting UI sorting logic decide runnable order instead of blocker-aware runtime rules.
- Reusing task-list identities after reset or branch changes and accidentally reconnecting stale background work.
- Collapsing abort, cancel, stop, and kill into one action even though they imply different process and state cleanup behavior.
- Treating a subagent-spawn primitive that was merely renamed (e.g. Claude Code's `Task` → `Agent`) as two distinct task families instead of checking whether it is the same primitive under an old name.
## Common Anti-Patterns
- Treating tasks as plain strings or chat messages instead of typed runtime objects.
- Using badge counts without a real background-task model.
- Assuming claim is just “set owner if empty” with no collision handling.
- Reusing task-list identifiers after reset because “the old list is gone.”
- Conflating abort, cancel, stop, and kill into one button.
- Letting UI sorting logic decide runnable order instead of blocker-aware runtime rules.
## Task Sizing Heuristic (OpenAI / Codex, 2026-05)
Source: [*How OpenAI uses Codex*](https://cdn.openai.com/pdf/6a2631dc-783e-479b-b1a4-af0cfbd38630/how-openai-uses-codex.pdf), p. 11 — internal-usage report.
- **Calibration point:** a well-scoped Codex task is one that *"would take you or a teammate about an hour to complete or a few hundred lines of code to implement."* This is the largest scoped unit OpenAI's own teams reliably run unattended in May 2026.
- **Pattern — sizing gate:** before queueing a task, ask "could a teammate do this in roughly an hour given the same prompt?" If no, break it down. If yes, dispatch.
- **Anti-pattern — multi-day asks framed as one task.** "Migrate the auth subsystem to OAuth 2.1" is not a task; it is a project. Decompose into sized tasks each gated by an Ask-Mode plan before dispatch (see [`../ai-coding-agents/SKILL.md#openai-internal-practice-codex-2026-05`](../ai-coding-agents/SKILL.md#openai-internal-practice-codex-2026-05)).
- **Forecast hook:** "as models improve, expect the size of the tasks it can take on to increase" (OpenAI). Re-baseline this heuristic every two minor model releases; do not treat the hour/few-hundred-LOC figure as a permanent ceiling.
- **Task queue as backlog:** the corollary to the sizing rule — small tasks dispatched freely become a working backlog rather than a planning burden. No obligation to produce a full PR per task; tangential and partial work is legitimate queue content.
## Claude Code Routines (2026-04)
Claude Code Routines is a research-preview scheduling layer that lets Claude Code run without a local session. Distinguish three trigger classes; pick the narrowest one that fits:
| Trigger class | Runtime | Persistence | Use when |
|---------------|---------|-------------|----------|
| **Routines** (Anthropic cloud) | Cloud-hosted Claude Code workers | Schedule, API call, or GitHub event fires the job with the laptop closed | You need cadence-driven or webhook-driven work (PR babysitting, nightly repo sweeps, scheduled reports) |
| **`/loop`** (session-bound) | Current local session | Dies with the session; runs while the session is open | You want a repeating task during an active working session (poll deploy, retry until green) |
| **Desktop scheduled tasks** | Local machine | Tied to this machine staying awake | Machine-local automation where cloud access is not acceptable |
Routines are task-shaped: a Routine creates tasks in the host runtime under a scheduler-owned task family. Apply the same typing, claiming, blocker, and cancellation rules as other task types — schedule triggers do not earn special cases.
Caveats (verified 2026-07-11 — still research preview, not GA; re-check before treating this as a stable contract):
- Daily run caps per plan (Pro: 5/day; Max: 15/day; Team/Enterprise: 25/day; overage billing available on metered plans). One-off (non-scheduled) fires draw down normal session usage instead and do **not** count against the daily cap — model it as a separate counter. Source: https://code.claude.com/docs/en/routines, https://claude.com/blog/introducing-routines-in-claude-code. Design for throttling and idempotent handlers regardless of tier.
- Each Routine invocation is a fresh session — depends on `AGENTS.md` / `CLAUDE.md` for context, not transcript memory.
- Treat Routine-spawned tasks as remote-task lifecycle, not local-agent lifecycle, for cancellation and ownership purposes.
- GitHub-event runs are capped per routine per hour; events beyond the cap are **dropped, not queued**.
Source: Matt Abrams, *Claude Code Routines* tutorial (2026-04-20); https://code.claude.com/docs/en/routines (re-verified 2026-07-11).
## Background-Subagent Task Semantics (Claude Code, 2026-06/07)
Claude Code's own subagent-task model changed enough in the last two months that the "task vs. subagent" naming and the background-permission contract in this skill's design rules need a concrete, dated anchor. Treat these as the reference implementation of the abstract rules above ("Differentiate abort from kill," "background eligibility"), not as a Claude-Code-only detail.
- **Naming collapse (v2.1.63):** the tool historically called `Task` for spawning subagents was renamed `Agent`. Legacy `Task(...)` references in settings and agent definitions still resolve as an alias; some SDK surfaces still report `Task` in an initial tools list while emitting `Agent` in tool-use blocks. When auditing an existing runtime, do not assume "Task" and "Agent" are different task families — check whether it is the same subagent-spawn primitive under an old name before modeling two families.
- **Background-by-default (v2.1.198, 2026-07-01):** subagent spawns run in the background by default rather than opt-in; the lead session keeps working and is notified on completion or when input is needed via a Notification hook (`agent_needs_input` / `agent_completed`). Design implication: "background" is no longer a rare, explicitly-requested task state — it is the default task shape, so background-task surfaces must be a first-class, always-visible part of the UI, not a rare drawer.
- **Permission-prompt routing fix (v2.1.186, 2026-06-22):** before this release, a background subagent that hit an unapproved tool call **silently auto-denied** the call and could stall in a read-only loop with no visible error — a textbook version of this skill's "background indicators showing tasks that are not actually executing" failure mode. Since v2.1.186, the permission prompt now **surfaces into the parent/lead session**, labeled with the subagent's name, and the lead can approve or deny inline while the subagent keeps running. Model this explicitly: a background task's "waiting on a tool permission" moment is a distinct pending-input sub-state that must route to the owning session, not a state that gets swallowed and reported as generic progress.
- **Fork-mode override:** the `CLAUDE_CODE_FORK_SUBAGENT` environment variable forces every subagent spawn into the background and removes the runtime's ability to force a specific spawn to the foreground (the `background` frontmatter field and any per-call foreground request are ignored). Treat this as a host-wide policy switch on background eligibility, not a per-task property — a runtime that lets individual task requests silently override a host-wide fork-mode policy has a policy-enforcement bug, not a feature.
Known trap to add to your own review checklist: a runtime that treats "background subagent needs a permission" as a silent, unrecoverable failure — rather than routing it to the owning session as a first-class pending state — reproduces a bug Anthropic had to ship a dedicated fix for. Verify current behavior against `code.claude.com/docs/en/sub-agents` before depending on exact version gates; permission-routing and background-default behavior are active areas of change.
## Cross-Platform Patterns (Goose)
Goose models task creation around **recipes**: declarative YAML units that replace ad-hoc free-text task spawning. Three patterns worth importing into the task runtime model.
### Recipes as typed task blueprints
A Goose recipe is a validated YAML file with `version / title / description / instructions / author / extensions / activities / prompt / parameters` (typed: `{key, input_type, requirement, description, default}`). Tasks spawn *from* a recipe, not from a free-form user string. Parameters are declared, activities are listed, required extensions are pinned.
- **Pattern:** when a task type is well-defined and reusable, promote it to a typed blueprint. Runtime validates the blueprint at load time (see `recipe-scanner`-style static checks), binds typed parameters, and spawns the task with a pinned extension manifest.
- **Anti-pattern:** treating every task as a free-text prompt and relying on prompt craft to make them repeatable. That collapses reuse, versioning, and validation into transcript memory.
- **Recipe:** ship a validator that checks YAML syntax, required fields, extension references (do the declared extensions actually exist?), and security gates (is this recipe allowed to spawn network-touching tools?). Goose's `recipe-scanner/` crate is a working reference.
### Capability-narrowed subagents
When a lead session spawns parallel subagents (code review lane, docs lane, file-processing lane), each subagent should carry its *own* extension manifest — typically a subset of the lead's. Goose models this explicitly; the subagent inherits no more capability than its recipe declares.
- **Pattern:** subagent spawn = recipe + parameters + capability-narrowed extension list. The narrower capability envelope is enforced at task-creation time, not at tool-call time.
- **Anti-pattern:** subagents inheriting the lead's full tool belt by default. That turns "delegate code review" into "let a sub-process touch anything the lead could touch" and defeats parallel-lane isolation.
- **Recipe:** in your task typing, add an `extensions: Vec<ExtensionRef>` field to the subagent-task family. Refuse to spawn if the subagent's declared extensions are not a subset of the lead's envelope.
### Sub-recipes — composable task blueprints
A Goose recipe can invoke another recipe as a step, not just call a tool. Sub-recipes let complex workflows compose from validated pieces instead of giant monolithic prompts.
- **Pattern:** model sub-recipe calls as a distinct task relationship ("spawned-by-recipe-step"), separate from lead→worker (delegation) and from tool calls (invocation). Ownership, cancellation, and blocker semantics follow the recipe tree.
- **Anti-pattern:** inlining sub-recipes into the parent's prompt text. That loses the validation, capability-narrowing, and telemetry attribution benefits of the recipe boundary.
- **Recipe:** give sub-recipe tasks a distinct lifecycle state (`pending-subrecipe`, `running-subrecipe`, `completed-subrecipe`) and a parent-recipe pointer. Cancellation of the parent should cascade; a failed sub-recipe should be a first-class blocker on the parent.
## Goal-Mode Loops (Codex `/goal`, Goose `/goal`, and analogues, 2026-05)
Codex exposes a `/goal` command and Goose (v1.36.0+) ships its own `/goal` loop — two first-party implementations of the same pattern: act → score → check goal → continue or terminate. The same loop shape can be built on top of any agent runtime (Claude Code subagent, custom SDK harness, scheduled Routine). Two failure modes dominate when the loop is wired to a vague target:
- **Early give-up.** Score function is unsatisfiable in the obvious direction → agent halts after a few minutes claiming "good enough."
- **Infinite flail.** Score function never converges → agent rewrites the same files indefinitely, burning tokens with no progress.
Both modes share the same root cause: the *check goal* step is underspecified. Fixes below are first-party patterns from Chris Hayduk (OpenAI, 2026-05-11).
### Pattern — Quantitative goal + constraints
Replace qualitative goals with a measurable target plus an explicit constraint set. The agent terminates when target ≥ threshold AND no constraint is violated.
- **Anti-pattern:** "Make the code better." / "Improve this paper."
- **Pattern:** "Reduce runtime of code in `specific_file.py` by 20% without causing regressions in existing unit and integration tests."
Required fields for any goal-mode prompt:
1. **Target metric** — runtime, accuracy, line count, lint score, completed-items count.
2. **Direction + magnitude** — "reduce by 20%", "raise above 0.85", "down to ≤ 50 lines".
3. **Constraint set** — tests that must keep passing, files that must not change, APIs that must not break.
4. **Termination signal** — exact command or file state the agent can read to confirm it is done.
### Pattern — Checklist-as-score (qualitative → quantitative)
When the real goal is qualitative (formatting compliance, style guide adherence, doc completeness), convert it to a binary checklist:
1. Extract the qualitative spec into a markdown checklist with N items (Hayduk's NeurIPS→ICML conversion produced 200+ items from a LaTeX style file).
2. Instruct the agent: "Goal complete when all N of N items are checked off."
3. Each item can itself be vague — the model reasons about per-item completion better than per-goal completion.
4. Have the agent mutate the checklist file as it works, so progress is persisted and inspectable.
Why this works: a fuzzy "is the paper formatted correctly?" decision becomes N narrow "is rule K satisfied?" decisions, each of which the model can self-check with reasonable reliability.
### Pattern — Tight feedback loop
The goal-mode loop is bounded by per-iteration scoring time. Drop scoring cost without compromising signal:
- For ML/training tasks: smaller model + subsampled dataset (Hayduk: NanoFold dataset cut scoring from days to minutes for protein-structure architecture search).
- For codebases: scoped test subset that exercises the affected path, not full suite.
- For builds: incremental builds, not clean rebuilds.
This is the same minification principle as dev-loop test speedups, applied to the *evaluation* step of an RL-style agent loop instead of to verification.
### When bare goal-mode is not enough
Goal-mode is a ralph-style loop: same prompt repeats with the goal-state read back each iteration. Ralph loops scale test-time compute effectively — Anthropic's BrowseComp data (linked from [Watts, 2026-05-07](https://x.com/jarrodwatts/status/2052372045829382430)) shows Sonnet 4.6 spending ~10× tokens yielded ~10 percentage points higher score. But a bare ralph loop hits three ceilings:
1. **Ambiguity bottleneck.** Each iteration's output is the next iteration's input. One underspecified decision early in the run direction-shifts everything downstream. No amount of token spend rescues a run whose target was vague. Fix: invest in a pre-loop interview/clarification phase (20–50 clarifying questions) that forces the human to decide upfront. See [`../dev-workflow-planning/SKILL.md#pre-loop-setup-phase-both-variants`](../dev-workflow-planning/SKILL.md) and `superpowers:brainstorming`.
2. **Single-context-window bottleneck.** Even with token budget headroom, a single agent's context window accumulates clutter and the model starts "deluding itself" (Watts, citing Boris Cherny). Separate context windows beat one big window. Fix: orchestrator + implementer + reviewer triad — fresh subagent context per task, reviewer judges work without implementer biases. See [`../agents-swarm-orchestration/SKILL.md`](../agents-swarm-orchestration/SKILL.md).
3. **Cross-context memory bottleneck.** Multi-day runs cross compaction boundaries; without filesystem-backed memory, new context windows lose the chain. Fix: filesystem-backed journaling per the Watts 4-file (bounded product) or Hayduk 3-file (exploratory) pattern. See [`../dev-workflow-planning/SKILL.md#long-horizon-agent-journaling`](../dev-workflow-planning/SKILL.md).
Goal-mode + ralph loop alone is the right primitive for short-horizon quantitative goals (Hayduk's NeurIPS→ICML, runtime-by-20% optimizations). For multi-day product building it is necessary but not sufficient — pair it with interview-led setup, multi-agent execution, and cross-context memory or accept that the run will drift.
### Anti-pattern — Letting compaction carry multi-day state
Goal-mode runs that last hours or days exceed the practical window of in-memory transcript compaction. Force state to the filesystem instead. See [Long-Horizon Agent Journaling](../dev-workflow-planning/SKILL.md#long-horizon-agent-journaling-3-file-pattern) in `dev-workflow-planning` for the canonical 3-file (`PLAN.md` / `EXPERIMENTS.md` / `EXPERIMENT_NOTES.md`) pattern.
### Recipe — Wiring a goal-mode task
1. Convert user intent → quantitative goal + constraint set. If qualitative, build the checklist first as a planning step.
2. Stand up the cheapest scoring command that still discriminates progress; record it in the task definition.
3. Seed `PLAN.md` with the initial approach. Mount `EXPERIMENTS.md` + `EXPERIMENT_NOTES.md` as the agent's persistent scratchpad.
4. Spawn the goal-mode task with a hard wall-clock cap and an iteration cap; both should be generous but finite.
5. On termination, check the *real* score (not the agent's self-reported claim) before accepting the result.
Source: Chris Hayduk, *Using Codex Goals Effectively* (2026-05-11). Generalizable beyond Codex — the same pattern works for Claude Code subagents driving toward a measurable target.
## Navigation
### References
- [`references/task-types-and-lifecycle.md`](references/task-types-and-lifecycle.md) — Task families, statuses, and background visibility rules
- [`references/task-list-coordination-and-teammate-routing.md`](references/task-list-coordination-and-teammate-routing.md) — File-watched task lists, claiming, blockers, and teammate routing
- [`references/claude-code-routines.md`](references/claude-code-routines.md) — Scheduled/triggered routines: schedule (cron), API `/fire`, and GitHub-event triggers; prompt-authoring rules, limits, and creation paths
- [`references/durable-trigger-integration.md`](references/durable-trigger-integration.md) — Durable trigger integration for reliable triggered runs
- [`references/webhook-and-queue-triggers.md`](references/webhook-and-queue-triggers.md) — Webhook- and queue-driven task triggers, including per-event budgets
- [`references/openai-codex-cloud-tasks-and-agent-graph.md`](references/openai-codex-cloud-tasks-and-agent-graph.md) — OpenAI Codex cloud task lifecycle, apply/diff result states, environment filters, and persisted task topology
### Scripts
- [`scripts/recipe_scanner.py`](scripts/recipe_scanner.py) — stdlib-only static validator for recipe blueprint YAML files
### Data
- [`data/sources.json`](data/sources.json) — Primary documentation and source references for coding-agent task runtime guidance
### Related Skills
- [`../agents-swarm-orchestration/SKILL.md`](../agents-swarm-orchestration/SKILL.md)
- [`../ai-coding-agents-terminal-ui/SKILL.md`](../ai-coding-agents-terminal-ui/SKILL.md)
- [`../ai-coding-agents-sessions/SKILL.md`](../ai-coding-agents-sessions/SKILL.md)
- [`../ai-agents/references/autonomous-loop-patterns.md`](../ai-agents/references/autonomous-loop-patterns.md) — when a trigger should drive an autonomous *loop* (iterate until a goal/acceptance criterion is met), not a single run
- [`../agents-hooks/references/budget-and-loop-hooks.md`](../agents-hooks/references/budget-and-loop-hooks.md) — budget, iteration-cap, stagnation, and kill-switch enforcement for scheduled/triggered runs and always-on bots
## Fact-Checking
- Known bugs, regressions, framework/compiler/runtime footguns, and version-specific crash or workaround guidance must be verified against current primary web sources before being treated as current fact.
- The patterns here are grounded in a local April 2026 `claude_code` snapshot, current primary runtime docs, and a 2026-07-11 web re-verification pass (Task→Agent rename, background-by-default default, and background-permission-prompt routing — see [Background-Subagent Task Semantics](#background-subagent-task-semantics-claude-code-2026-0607)). Re-check current task type names, subagent or handoff affordances, session persistence rules, and backgrounding behavior before depending on exact runtime details — this cluster changes fast.
## Learnings Loop
Before applying this skill on a non-trivial task, read `learnings.consolidated.md` in this directory (and `learnings.md` if present).
After applying it, if you encountered a pattern worth remembering, a mistake worth preventing, or a domain fact that surprised you, append one dated bullet to `learnings.md` via `agents-skills-feedback-loop/scripts/append_learning.py`. Do not modify `SKILL.md` itself.