Skip to main content

How to test and operate Plan Review

Plan review is an optional human-oversight gate for native talon run agents — most Talon deployments (gateway proxying, coding agents) never enable it, and none of the four operating pillars depend on it. This operator guide covers Talon's Plan Review Gate: when enabled, it pauses agent execution before the first LLM call until an operator approves the execution plan, with every decision recorded in signed evidence. It provides supporting controls for EU AI Act Art. 14-style human oversight — not a compliance claim.

Not covered here: per-tool approval during the agentic loop (/v1/tool-approvals). That is a separate mid-run gate. See Operational control plane.


Lifecycle

sequenceDiagram
participant Operator
participant Talon
participant LLM

Operator->>Talon: talon run or POST /v1/agents/run
Talon->>Talon: Policy + classify + build execution plan
Talon-->>Operator: plan_pending — no LLM call yet
Operator->>Talon: Approve or reject (CLI, API, dashboard)
alt Approved and talon serve running
Talon->>LLM: Auto-dispatch within ~2s
else Approved without serve
Operator->>Talon: talon plan execute
Talon->>LLM: Manual dispatch
end
Talon-->>Operator: Response + signed evidence
Plan statusMeaning
pendingAwaiting human decision
approvedDecision recorded; not yet executed
rejectedBlocked for this plan
dispatchedExecution completed (or dispatch failed)

Prerequisites

export TALON_SECRETS_KEY="$(openssl rand -hex 32)"
export OPENAI_API_KEY="sk-..." # or: talon secrets set openai-api-key ...
export TALON_ADMIN_KEY="strong-admin-key" # API/dashboard approvals

mkdir my-workspace && cd my-workspace
talon init --scaffold --name my-agent
talon secrets set openai-api-key "$OPENAI_API_KEY"

Policy configuration

In agent.talon.yaml under compliance:

Force review on every run (best for first E2E test)

compliance:
human_oversight: "always"

Selective review (production)

compliance:
human_oversight: "on-demand"
plan_review:
require_for_tools: true
cost_threshold: 0.10
require_for_tier: "tier_2"
volume_threshold: 50
human_oversightBehavior
"always"Every run gated
"on-demand"Gated only when a plan_review rule fires
"none" / omittedNo plan gate

When does on-demand fire?

Evaluated in the Plan Review Gate before the LLM call:

RuleFires when
require_for_tools: trueRuntime tool registry has ≥1 registered tool
cost_thresholdPre-run cost estimate (EUR) ≥ threshold
require_for_tier: "tier_2"Classified input tier ≥ 2 (e.g. PII in prompt)
volume_thresholdDestructive verb + number > threshold in plan text (intent/tool path; see limitation below)

allowed_tools is not require_for_tools

capabilities:
allowed_tools: [sql_query, file_read] # policy allowlist only

The gate checks len(toolRegistry.List()) > 0, not allowed_tools. On talon run, the registry starts empty unless tools are registered at runtime. Listing tools in policy alone does not trigger require_for_tools.

talon intent classify is not talon run

talon intent classify summarize '{"prompt":"..."}'
# Plan review: true

intent classify answers: if this tool were invoked, would review be required? It simulates hasTools: true.

talon run "..." answers: should this run gate before any LLM call? It uses the real registry, input tier, and pre-run cost estimate.

Known limitation: volume_threshold on user prompts may not fire on talon run today because the gate does not pass prompt text into RequiresReview. Volume rules apply reliably via talon intent classify and tool-intent paths.


Path A — CLI only (no talon serve)

# 1. Trigger — expect plan id, NO LLM answer
talon run "Summarize EU AI Act milestones for compliance teams"
# ✓ Plan pending human review: plan_<id>

# 2. List pending
talon plan pending --tenant default

# 3. Approve
talon plan approve plan_<id> --tenant default --reviewed-by <your-name>

# 4. Execute (required when serve is not running)
talon plan execute plan_<id> --tenant default
# ✓ Evidence stored: req_<id>

# 5. Reject (optional)
talon plan reject plan_<id> --tenant default \
--reviewed-by <your-name> --reason "Scope too broad"

Approve alone does not run the agent when talon serve is not running.


Path B — talon serve + auto-dispatch

# Terminal 1
export TALON_ADMIN_KEY="..."
talon serve --port 8080

# Terminal 2 — trigger
talon run "Summarize EU AI Act milestones for compliance teams"

# Approve (CLI or API — both update the same plan store)
talon plan approve plan_<id> --tenant default --reviewed-by <your-name>

Or via API:

curl -X POST "http://localhost:8080/v1/plans/plan_<id>/approve" \
-H "X-Talon-Admin-Key: $TALON_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"reviewed_by":"<your-name>"}'

The auto-dispatcher polls every ~2s. No talon plan execute needed when serve is running.

Verify dispatch:

curl -s -H "X-Talon-Admin-Key: $TALON_ADMIN_KEY" \
'http://localhost:8080/v1/evidence?limit=5&invocation_type=plan_dispatch' | jq .

Path C — Dashboard

http://localhost:8080/dashboard?talon_admin_key=$TALON_ADMIN_KEY
  1. Trigger a gated run (talon run or API).
  2. Open Plans Awaiting Review.
  3. Approve or reject (reject prompts for a reason).
  4. With talon serve running, execution starts automatically after approve.

HTTP API

ActionMethodAuth
Trigger runPOST /v1/agents/runAgent key (native-only serve); admin key when a gateway is served
List pendingGET /v1/plans/pendingAgent key or admin key
Get planGET /v1/plans/{id}Agent key or admin key
ApprovePOST /v1/plans/{id}/approveAdmin key only
RejectPOST /v1/plans/{id}/rejectAdmin key only
ModifyPOST /v1/plans/{id}/modifyAdmin key only

Admin header: X-Talon-Admin-Key: &lt;key> (Bearer fallback accepted). Full matrix: Authentication and key scopes.

Trigger example:

curl -s -X POST http://localhost:8080/v1/agents/run \
-H "Authorization: Bearer <agent-key-value>" \
-H "Content-Type: application/json" \
-d '{"tenant_id":"default","agent_name":"my-agent","prompt":"Your query"}' | jq .

With human_oversight: "always", the response includes plan_pending and session_id (HTTP 200 or 202).


Validated test matrix

Run one block at a time. Do not paste markdown headings or comment lines into the shell.

Edit YAML without yq (works on minimal Ubuntu):

# Switch to on-demand
sed -i 's/human_oversight:.*/human_oversight: "on-demand"/' agent.talon.yaml
grep human_oversight agent.talon.yaml

# Switch to always
sed -i 's/human_oversight:.*/human_oversight: "always"/' agent.talon.yaml
TestConfigCommandExpected
Low risk passeson-demandtalon run "Summarize EU AI Act milestones"Full LLM answer, no plan_pending
PII gateson-demand + require_for_tier: tier_2talon run "Customer: jan.kowalski@example.com IBAN DE89370400440532013000"plan_pending, no LLM body
Always gatesalwaystalon run "test"plan_pending
Intent previewanytalon intent classify delete_records '{"count": 10000}'Plan review: true (informational only)

Use real plan IDs from talon plan pending — never paste the literal placeholder plan_&lt;id>.


Evidence

invocation_typeWhen
plan_reviewApprove/reject decision
plan_dispatchApproved plan executed (serve auto or plan execute)
(default)Final LLM response

Approval is audited even if execution never happens.


E2E checklist

  • talon run prints Plan pending human review: plan_&lt;id> with no LLM body
  • talon plan pending lists the plan
  • talon plan approve succeeds; plan leaves pending list
  • Without serve: talon plan execute returns LLM output + evidence
  • With serve: plan_dispatch evidence within ~2s of approve
  • Reject: plan never executes; reason in evidence
  • Agent key cannot POST .../approve (401/403)

Smoke parity: tests/smoke_sections/24_plan_dispatch.sh.

Structured testcase inventory and phased E2E script: Plan Review E2E test case.


Troubleshooting

SymptomCauseFix
Run completes immediatelyon-demand and no rule firedUse human_oversight: "always" for testing
intent classify true but run not gatedTool simulator vs run gateSee sections above
Approved but no LLM outputServe not running; no plan executeRun talon plan execute &lt;id>
PII plan fails at executeTier 2 → Bedrock not configuredAdjust tier_2 routing or configure Bedrock
/v1/evidence 401No keyed agent on minimal serveUse admin key
plan_&lt;id>: No such filePasted placeholder literallyUse IDs from talon plan pending
Both runs gated after "on-demand" testhuman_oversight still alwaysgrep human_oversight agent.talon.yaml