Home > EulerAgent > Tutorials > Basic > 03. HITL Approval Workflow — Review, Modify,...

03. HITL Approval Workflow — Review, Modify, and Execute Proposals


Learning Objectives

By the end of this tutorial, you will be able to:


Prerequisites

euleragent new my-assistant --template personal-assistant
euleragent new code-agent --template code-assistant

What Is HITL Approval?

HITL (Human-In-The-Loop) is a mechanism where the agent obtains human review and approval before performing dangerous operations.

euleragent follows the deny-all principle, and the following tools always require human approval:

Tool Risk Level Reason
file.write medium File system modification
file.delete high Permanent data deletion
shell.exec high Arbitrary code execution
web.search medium External network communication
web.fetch medium External URL access
git.commit medium Codebase modification
git.push high Remote repository modification
email.send high External email dispatch
payment.charge high Financial transaction
llm.external_call high External LLM API cost

In addition to tool calls, euleragent also supports system-level approvals:

Approval Kind Risk Level Reason
source_enable medium MCP source activation — connecting to external search services
llm_profile_enable high External LLM profile activation — when using external LLMs via --llm-plan / --llm-final

Note: kind: source_enable approvals are automatically generated when SearchRouter tries to activate a new MCP source. kind: llm_profile_enable approvals are auto-generated by the Runner when you specify an is_external: true profile via --llm-plan / --llm-final. The run continues even without approval (falling back to the local default provider), and after approval, the external profile takes effect from the next run. For detailed hands-on practice with MCP source approval, see 08_mcp_provider_and_tools.md. For the complete external LLM profile approval cycle, see 09_scoped_llm_profile.md.


Step-by-Step Guide

Step 1: Generate Pending Approvals

Run a task that includes multiple tool calls for approval practice:

euleragent run my-assistant \
  --task "report.md 파일을 만들고, AI 에이전트 프레임워크를 웹에서 검색한 후, 결과를 파일에 저장해줘" \
  --mode plan \
  --max-loops 3

Expected output:

Run a1b2c3d4e5f6 started (agent: my-assistant, mode: plan)

[loop 1/3] Generating plan...
  → Proposed: web.search (risk: medium)
      query: "AI agent framework 2025 comparison"

[loop 2/3] Continuing...
  → Proposed: file.write (risk: medium)
      path: report.md

Run a1b2c3d4e5f6 completed (state: PENDING_APPROVAL)

2 approval(s) pending.
  Use: euleragent approve list --run-id a1b2c3d4e5f6

Step 2: Check the Approval List

euleragent approve list

Expected output:

Pending approvals (2):

  ID              TOOL         RISK     RUN              STATUS    CREATED
  apv_w1x2y3      web.search   medium   a1b2c3d4e5f6     pending   10:30:00
  apv_p4q5r6      file.write   medium   a1b2c3d4e5f6     pending   10:30:02

To see approvals for a specific run only:

euleragent approve list --run-id a1b2c3d4e5f6

To see approvals for a specific tool only:

euleragent approve list --tool web.search

Step 3: View Approval Record Details

Review all fields of an approval record:

euleragent approve show apv_w1x2y3

Expected output:

{
  "id": "apv_w1x2y3",
  "run_id": "a1b2c3d4e5f6",
  "tool_name": "web.search",
  "params": {
    "query": "AI agent framework 2025 comparison",
    "top_k": 5
  },
  "risk_level": "medium",
  "side_effects": ["external_network"],
  "status": "pending",
  "created_at": "2026-02-23T10:30:00Z",
  "agent": "my-assistant",
  "loop": 1,
  "context": "Task: report.md 파일을 만들고 AI 에이전트 프레임워크를 웹에서 검색..."
}

Meaning of each field:

Field Description
id Unique identifier for the approval record
run_id The run ID that generated this approval
tool_name Name of the tool to be executed
params Parameters to be passed to the tool
risk_level Risk level (low/medium/high)
side_effects List of expected side effects
status Current status (pending/accepted/rejected/executed)
created_at Approval record creation time
agent The agent that generated this approval
loop Agentic loop number
context Context in which this approval was generated

Step 4: Accept an Individual Approval

Accept after reviewing the content:

euleragent approve accept apv_w1x2y3 --actor "user:you"

Expected output:

Accepted: apv_w1x2y3 (web.search)
  Status: accepted (not yet executed)
  Use --execute flag to execute immediately.

Accepting without --execute changes the status to accepted but does not actually execute yet. You can batch execute later or execute explicitly.

To execute immediately:

euleragent approve accept apv_w1x2y3 --actor "user:you" --execute

Expected output:

Accepted and executed: apv_w1x2y3 (web.search)
  Result: 5 search results retrieved
  Stored in: .euleragent/runs/a1b2c3d4e5f6/tool_calls.jsonl

Required: You must provide approver information via the --actor flag or the EULERAGENT_ACTOR environment variable (deny-all-default policy): bash euleragent approve accept apv_w1x2y3 --actor "user:alice" --execute Setting the EULERAGENT_ACTOR environment variable eliminates the need to type --actor every time.


Step 5: Modify Parameters Before Approving

You can modify tool parameters before approving. For example, if a search query is too broad, make it more specific:

euleragent approve accept apv_w1x2y3 \
  --actor "user:you" \
  --edit-params '{"query": "open source AI agent framework Python 2025", "top_k": 3}'

Expected output:

Accepted with modified params: apv_w1x2y3 (web.search)
  Original query: "AI agent framework 2025 comparison"
  Modified query: "open source AI agent framework Python 2025"
  top_k: 5 → 3

Modify parameters and execute immediately:

euleragent approve accept apv_w1x2y3 \
  --actor "user:you" \
  --edit-params '{"query": "open source AI agent framework Python 2025", "top_k": 3}' \
  --execute

When to use --edit-params: - When the search query contains sensitive information - When changing the file save path to a safer location - When parameters are too broad and need to be narrowed - When adjusting LLM-generated parameters for better accuracy


Step 6: Reject an Approval

Dangerous or inappropriate tool calls can be rejected:

euleragent approve reject apv_p4q5r6 --actor "user:you" --reason "File path is not safe. Must save under /tmp/."

Expected output:

Rejected: apv_p4q5r6 (file.write)
  Reason: File path is not safe. Must save under /tmp/.
  Status: rejected

The rejection reason is recorded in the audit log for later tracking. Rejected approvals are not executed, and the agent can check this fact in subsequent runs.


Step 7: Batch Accept — Run ID Filter

Accept all approvals from the same run at once:

euleragent approve accept-all --run-id a1b2c3d4e5f6 --actor "user:you"

Expected output:

Accepted 2 approval(s) for run a1b2c3d4e5f6.
  [accepted] web.search (apv_w1x2y3)
  [accepted] file.write (apv_p4q5r6)
  Status: accepted (not yet executed)

Including immediate execution:

euleragent approve accept-all --run-id a1b2c3d4e5f6 --actor "user:you" --execute

# Batch accept with actor information
euleragent approve accept-all --run-id a1b2c3d4e5f6 --actor "ops:batch-review" --execute

Expected output:

Accepted and executed 2 approval(s) for run a1b2c3d4e5f6.
  [OK] web.search (apv_w1x2y3) — 5 results retrieved
  [OK] file.write (apv_p4q5r6) — report.md created (1.2KB)
Executed 2/2 successfully.

Run a1b2c3d4e5f6 state: RUN_FINALIZED

Step 8: Batch Accept — Tool Filter

Process only approvals for a specific tool type:

# Accept only web.search approvals
euleragent approve accept-all --tool web.search --actor "user:you" --execute
# Accept only file.write approvals (across all runs)
euleragent approve accept-all --tool file.write --actor "user:you"
# Accept only web.search approvals for a specific run
euleragent approve accept-all --run-id a1b2c3d4e5f6 --tool web.search --actor "user:you" --execute

Expected output:

Accepted 1 approval(s) matching tool=web.search.
  [OK] web.search (apv_w1x2y3) — executed
1 approval(s) remain pending (different tool type).

Step 9: Batch Reject

Reject all approvals for a risky run:

euleragent approve reject-all --run-id a1b2c3d4e5f6 --actor "user:you" --reason "Security review required"

Expected output:

Rejected 2 approval(s) for run a1b2c3d4e5f6.
  [rejected] web.search (apv_w1x2y3)
  [rejected] file.write (apv_p4q5r6)
  Reason: Security review required

Step 10: Handling Policies by Risk Level

Adjust review intensity based on risk level.

Low Risk Tools

Tools like file.read, memory.search, and git.status are not included in require_approval by default and are auto-executed. No separate approval is needed.

Medium Risk Tools

Tools like file.write, web.search, and git.commit are included in require_approval and require approval. In most cases, review the content and accept.

Recommended review items: - file.write: Verify the save path is within the workspace - web.search: Check that the query contains no sensitive information - git.commit: Verify the commit message and changed files are as intended

High Risk Tools

Tools like shell.exec, file.delete, email.send, payment.charge, and git.push should always be reviewed carefully.

# Example: view shell.exec approval details
euleragent approve show apv_s1h2e3
{
  "id": "apv_s1h2e3",
  "tool_name": "shell.exec",
  "params": {
    "command": "rm -rf ./old_data/",
    "working_dir": "/home/user/my-project"
  },
  "risk_level": "high",
  "side_effects": ["filesystem_modification", "irreversible"],
  "status": "pending"
}

High risk tool review checklist: - [ ] Is the command/action what was expected? - [ ] Is the scope limited to what was intended? - [ ] Is this an irreversible operation? (Is there a backup?) - [ ] Does the data being sent externally contain sensitive information?


Step 10-1: MCP Source Activation Approval (kind: source_enable)

When the agent calls web.search, the internal SearchRouter evaluates candidate sources from mcp.sources in workspace.yaml. When activating a new source for the first time, a kind: source_enable approval is generated.

Viewing a Source Activation Approval Record

euleragent approve show apv_src_t1v2

Expected output:

{
  "id": "apv_src_t1v2",
  "run_id": "a1b2c3d4e5f6",
  "kind": "source_enable",
  "tool_name": "web.search",
  "params": {
    "query": "AI agent framework 2025 comparison"
  },
  "resolved_source_id": "brave",
  "candidate_sources": ["brave", "tavily", "local_kb"],
  "routing_reason": "brave scored highest for broad web queries (score: 0.91)",
  "risk_level": "medium",
  "side_effects": ["external_network", "api_key_usage"],
  "status": "pending",
  "created_at": "2026-02-23T10:30:00Z",
  "agent": "my-assistant",
  "loop": 1,
  "context": "SearchRouter selected 'brave' from source-set 'default'"
}

Additional fields compared to regular tool approval records:

Field Description
kind Approval type — source_enable is an MCP source activation approval
resolved_source_id The final source ID selected by SearchRouter
candidate_sources List of evaluated candidate sources
routing_reason Reason why SearchRouter selected this source

Source Override — Switch to a Different Source

If the source selected by SearchRouter is not appropriate, you can specify a different one with --edit-params:

euleragent approve accept apv_src_t1v2 \
  --actor "user:you" \
  --edit-params '{"resolved_source_id": "tavily"}' \
  --execute

Expected output:

Accepted with modified params: apv_src_t1v2 (source_enable)
  Original source: brave
  Override source: tavily
  Executed: web.search via tavily — 5 results retrieved

This way, humans maintain ultimate control over which external service is used. Only sources included in candidate_sources can be specified as override targets.

Tip: To always prioritize a specific source, combine --source-set with the source priority settings in workspace.yaml. This reduces the need for manual overrides each time.


Step 11: Verify the Audit Trail

All approval activities are recorded in the audit log:

euleragent logs a1b2c3d4e5f6

Expected output:

Run: a1b2c3d4e5f6
  State: RUN_FINALIZED

Approvals:
  apv_w1x2y3  web.search   accepted  2026-02-23 10:30:30  by=user:alice
  apv_p4q5r6  file.write   accepted  2026-02-23 10:30:32  by=user:alice

Tool Executions:
  web.search   apv_w1x2y3  OK   3 results
  file.write   apv_p4q5r6  OK   report.md (1.2KB)

Raw approval log file:

cat .euleragent/runs/a1b2c3d4e5f6/approvals.jsonl
{"id": "apv_w1x2y3", "tool_name": "web.search", "status": "accepted", "executed": true, "executed_at": "2026-02-23T10:30:30Z"}
{"id": "apv_p4q5r6", "tool_name": "file.write", "status": "accepted", "executed": true, "executed_at": "2026-02-23T10:30:32Z"}

Complete Approval Workflow Diagram

euleragent run <agent> --task "..." --mode plan
            │
            ▼
    [LLM proposes tool calls]
            │
            ├─ Regular tool call ─────────────────┐
            │                                   │
            ├─ web.search call ──┐               │
            │                    ▼               │
            │              [SearchRouter]        │
            │              Evaluate & select     │
            │              source                │
            │                    │               │
            │              New source activation?│
            │               ├─ Yes ──▶ [kind: source_enable approval generated]
            │               └─ No                │
            │                    │               │
            ▼                    ▼               ▼
    [Approval record created] → saved to .euleragent/approvals/
    {
      "id": "apv_xxx",
      "kind": "tool_call | source_enable",
      "tool_name": "web.search",
      "params": {...},
      "resolved_source_id": "brave",        ← for source_enable
      "candidate_sources": ["brave", ...],  ← for source_enable
      "routing_reason": "...",              ← for source_enable
      "risk_level": "medium",
      "status": "pending"
    }
            │
            ▼
    [Human reviews] euleragent approve list / show
            │
     ┌──────┴──────┐
     │             │
  Accept         Reject
     │             │
  --edit-params  --reason
  (optional)       │
     │             ▼
     ▼        [rejected] → recorded in audit log
  --execute
  (optional)
     │
     ▼
  [Tool executed] → recorded in tool_calls.jsonl
     │
     ▼
  [Result added to context]
     │
     ▼
  [Next loop or complete]

Expected Output Summary

# Approval list
$ euleragent approve list
Pending approvals (2):
  apv_w1x2y3  web.search  medium  a1b2c3d4e5f6  pending
  apv_p4q5r6  file.write  medium  a1b2c3d4e5f6  pending

# Individual accept + execute
$ euleragent approve accept apv_w1x2y3 --actor "user:you" --execute
Accepted and executed: apv_w1x2y3 (web.search)
  Result: 5 search results retrieved

# Parameter modification + accept
$ euleragent approve accept apv_w1x2y3 --actor "user:you" --edit-params '{"query": "..."}' --execute
Accepted with modified params: apv_w1x2y3 (web.search)

# Reject
$ euleragent approve reject apv_p4q5r6 --actor "user:you" --reason "Path not safe"
Rejected: apv_p4q5r6 (file.write)

# Batch accept + execute
$ euleragent approve accept-all --run-id a1b2c3d4e5f6 --actor "user:you" --execute
Accepted and executed 2/2 successfully.

FAQ / Common Errors

Q: Where are approval records stored as JSONL files?

Approval records are stored in two locations: 1. .euleragent/approvals/ — The unified approval queue (managed regardless of run ID) 2. .euleragent/runs/<run-id>/approvals.jsonl — Approval records for a specific run

# Full approval queue
ls .euleragent/approvals/

# Approval records for a specific run
cat .euleragent/runs/a1b2c3d4e5f6/approvals.jsonl

Q: What happens if an approved tool fails during execution?

On execution failure, the error is recorded in tool_calls.jsonl and the approval status changes to executed_failed. Execution continues, but the agent's subsequent output may be incomplete since the tool's result is missing.

# Check failed tools
cat .euleragent/runs/<run-id>/tool_calls.jsonl | grep "status.*failed"

Q: How do I handle quotes when writing JSON in --edit-params?

When passing JSON in the shell, wrap the entire argument in single quotes and use double quotes inside:

euleragent approve accept apv_xxx \
  --actor "user:you" \
  --edit-params '{"query": "LangChain vs CrewAI 2025", "top_k": 3}'

Escaping is different on Windows PowerShell:

euleragent approve accept apv_xxx `
  --actor "user:you" `
  --edit-params '{\"query\": \"LangChain vs CrewAI 2025\", \"top_k\": 3}'

Q: I accidentally approved and executed with the wrong parameters. Can I undo it?

The results of executed tools cannot be undone (especially file.write or shell.exec). This is why Plan mode and --edit-params are recommended. For file system changes, the change history is recorded in the diffs/ directory:

ls .euleragent/runs/<run-id>/diffs/
cat .euleragent/runs/<run-id>/diffs/report.md.diff

Q: I ran euleragent approve accept-all but some approvals were not processed.

A --run-id or --tool filter may be applied, processing only approvals matching those conditions. Also, approvals already in another state (accepted/rejected) are excluded from processing.

# Check all pending approvals (no filter)
euleragent approve list

# Check all pending approvals for a specific run
euleragent approve list --run-id a1b2c3d4e5f6

Q: How do I completely block high-risk tools for a specific agent?

Add them to the denylist in the agent's tools.yaml:

# .euleragent/agents/my-assistant/tools.yaml
policy: deny-all
denylist:
  - shell.exec
  - file.delete
  - email.send
  - payment.charge

Or in agent.yaml:

# .euleragent/agents/my-assistant/agent.yaml
tools_denylist:
  - shell.exec
  - file.delete

Q: Typing --actor every time is tedious.

Set the EULERAGENT_ACTOR environment variable to have actor information recorded automatically without the --actor flag:

# Add to shell profile (~/.bashrc, ~/.zshrc, etc.)
export EULERAGENT_ACTOR="user:$(whoami)"

# Or in CI/CD pipelines
export EULERAGENT_ACTOR="ci:${CI_PIPELINE_ID}"

If the --actor flag is explicitly provided, it takes precedence over the environment variable.


Common Mistakes (Out-of-Order Steps)

Symptom Cause Fix
Error: Approval 'X' not found. Incorrect approval ID euleragent approve list
Error: Approval 'X' is already accepted. Attempted to re-accept an already accepted approval euleragent approve list --run-id <id>
Error: Actor identity is required --actor missing --actor "user:name" or export EULERAGENT_ACTOR="..."
Next: euleragent workflow resume <id> Auto-hint after accept-all Run the command shown in the hint

Next step: 04_task_file_and_batch.md — Learn about task files, variable substitution, and batch research.

← Prev Back to List Next →