Example
May 10, 2026 · View on GitHub
A four-stage V2 automation showing explicit depends_on, per-stage
output_contract, and an approval gate before the final stage.
Use case: weekly review automation. Reads recent commits + open PRs, drafts a markdown review, runs a verifier role, and posts to Slack after approval.
Requires MCP servers:
composio(or equivalent) for GitHub and Slack tools.
Intent
Every Friday at 16:00 UTC, summarise the week's commits and open PRs in
<owner/repo>, run a verifier pass, and post a structured summary to #weekly-review. Don't post until I review.
Generated V2 automation payload
{
"name": "weekly-engineering-review",
"status": "paused",
"creator_id": "codex-plugin",
"workspace_root": ".",
"schedule": {
"type": "cron",
"cron_expression": "0 16 * * 5",
"timezone": "UTC",
"misfire_policy": { "type": "run_once" }
},
"external_integrations_allowed": true,
"handoff_config": { "auto_approve": false },
"agents": [
{
"agent_id": "scout",
"display_name": "Repo Scout",
"model_policy": { "default_model": { "provider_id": "openrouter", "model_id": "openai/gpt-4o-mini" } },
"tool_policy": { "allowlist": ["read"] },
"mcp_policy": {
"allowed_servers": ["composio"],
"allowed_tools": [
"mcp.composio.github_commits_list",
"mcp.composio.github_pulls_list"
]
},
"approval_policy": "auto"
},
{
"agent_id": "writer",
"display_name": "Review Writer",
"model_policy": { "default_model": { "provider_id": "openrouter", "model_id": "anthropic/claude-3.5-sonnet" } },
"tool_policy": { "allowlist": ["read", "write"] },
"mcp_policy": { "allowed_servers": [], "allowed_tools": [] },
"approval_policy": "auto"
},
{
"agent_id": "verifier",
"display_name": "Review Verifier",
"model_policy": { "default_model": { "provider_id": "openrouter", "model_id": "anthropic/claude-3.5-sonnet" } },
"tool_policy": { "allowlist": ["read"] },
"mcp_policy": { "allowed_servers": [], "allowed_tools": [] },
"approval_policy": "auto"
},
{
"agent_id": "publisher",
"display_name": "Slack Publisher",
"model_policy": { "default_model": { "provider_id": "openrouter", "model_id": "openai/gpt-4o-mini" } },
"tool_policy": { "allowlist": ["read", "write"] },
"mcp_policy": {
"allowed_servers": ["composio"],
"allowed_tools": ["mcp.composio.slack_message_send"]
}
}
],
"flow": {
"nodes": [
{
"node_id": "scout",
"agent_id": "scout",
"objective": "Collect the week's commits and open PRs.",
"prompt": "ROLE: Repo scout.\n\nINPUTS:\n- repo: <owner/name>\n- since: 7 days ago\n\nTASK:\n1. mcp.composio.github_commits_list since=7d.\n2. mcp.composio.github_pulls_list state=open.\n3. Bundle into a structured payload.\n\nREQUIRED OUTPUT (output_contract: structured_json):\n- payload: { commits: [...], open_prs: [...] }\n- schema_version: \"1\"\n- success_criteria:\n - payload.commits is an array (may be empty)\n - payload.open_prs is an array (may be empty)",
"output_contract": "structured_json"
},
{
"node_id": "draft",
"agent_id": "writer",
"depends_on": ["scout"],
"objective": "Draft a Friday review markdown.",
"prompt": "ROLE: Review writer.\n\nINPUTS:\n- upstream:structured_json.payload.commits[]\n- upstream:structured_json.payload.open_prs[]\n\nTASK:\n1. Group commits by author and area.\n2. List open PRs by status (ready / blocked / draft).\n3. Write the review to file://reports/weekly-review-<YYYY-MM-DD>.md.\n\nREQUIRED OUTPUT (output_contract: artifact):\n- artifact_kind: \"file\"\n- location: file path\n- artifact_summary: \"Weekly review draft for <YYYY-MM-DD>\"\n- success_criteria:\n - file exists and contains the date in its title\n - file is at most 1500 words",
"output_contract": "artifact"
},
{
"node_id": "verify",
"agent_id": "verifier",
"depends_on": ["draft"],
"objective": "Sanity-check the draft.",
"prompt": "ROLE: Verifier.\n\nINPUTS:\n- upstream:artifact.location (markdown file)\n- upstream-2:structured_json.payload\n\nTASK:\n1. Read the draft.\n2. Check every PR/commit referenced in the draft also exists in the upstream payload.\n3. Flag any hallucinated identifiers.\n\nREQUIRED OUTPUT (output_contract: review_decision):\n- decision: \"approve\" | \"request_changes\" | \"noop\"\n- rationale: short string\n- blocking_issues[]: list of hallucinated identifiers, if any\n- success_criteria:\n - decision is one of the three enums\n - blocking_issues[] is non-empty iff decision is \"request_changes\"",
"output_contract": "review_decision"
},
{
"node_id": "publish",
"agent_id": "publisher",
"depends_on": ["verify"],
"objective": "Post the verified review to Slack.",
"prompt": "ROLE: Slack publisher.\n\nINPUTS:\n- upstream-2:artifact.location (markdown file)\n- upstream:review_decision.decision\n\nTASK:\n1. If upstream decision is not \"approve\", stop with reason \"verifier rejected\".\n2. Otherwise, read the markdown file.\n3. Format as a Slack message (chunk if > 4000 chars).\n4. Call mcp.composio.slack_message_send to #weekly-review.\n\nCONSTRAINTS:\n- Requires human approval before slack_message_send runs.\n- Halt entirely if upstream verifier said request_changes.\n\nREQUIRED OUTPUT (output_contract: artifact):\n- artifact_kind: \"channel_post\"\n- location: Slack channel id + message ts\n- artifact_summary: \"Posted weekly review to #weekly-review\"\n- success_criteria:\n - location is non-empty\n - message ts is set",
"output_contract": "artifact"
}
]
}
}
Approval gates
verifyis auto: it's read-only and just outputs a decision.publishis gated: a human reviewer approves the Slack post after seeing the draft and the verifier's verdict.verify.decision == "request_changes"halts the publish stage even if a human approves it (the prompt explicitly stops in that case).
Why four stages
- Scout is its own stage so the verifier has a clean upstream payload to cross-check against.
- Draft writes to a workspace file so it's auditable before any external write.
- Verify is a separate role with its own model so it can be stronger than the writer if you want.
- Publish is the only stage with
mcp_policy.allowed_toolscontaining a write tool. Everything before it is read-only.