Documentación
Human gates
Where a person approves, rejects or redirects the work.
En esta página
The stage that runs nothing
A stage whose kind is HumanGate stops automation. The scheduler moves the task
into it and ends the run as awaiting human — no agent is dispatched, no
worktree is touched, nothing advances until a person decides. In the default
pipeline that stage is called Loop, and it sits between Review and PR Open.
Its prompt states the contract the previous stage has to satisfy:
This is the human Loop gate. Stop autonomous execution and present the
deliverable, verification evidence, Review summary, risks, and clear testing
instructions. Wait for the developer to test, approve, or request changes.
Human feedback must be added to task context and sends the task back to Build.
Human approval advances the task to PR Open.You can have more than one. Any stage you create with kind HumanGate behaves
the same way, wherever it sits in the list. The GStack preset ends on one (its
QA Gate); the govctl preset ends on one (its Approve stage).
Deciding in the desktop
Right-click a card — or press Space with it focused — for the action menu:
≣ Open detail | ± Git Diff | → Advance | ✓ Approve | ↩ Send back | ✕ Delete✓ Approvecloses the task, then releases any dependent tasks it was blocking — each gets awave: unblocked by #N — auto-startedcomment and starts running.↩ Send backmoves the task to the previous pipeline stage.→ Advancemoves it forward one stage without closing it.
The task detail screen carries the same decision in its Status picker, whose
last option is ✓ Approve & close. That one asks for confirmation first —
Closing a task is irreversible. Continue?
Deciding from a shell
tde task approve 41 --response "Verified locally against the staging DB"
tde task reject 41 --reason "Migration is missing a down step"approve requires the task to actually be at a human-gate stage, otherwise it
returns Task is not awaiting approval; an already-closed task returns
Task is already approved.
reject moves the task to the stage immediately before its current one, clears
the blocked flag, resets the run state to idle, and writes a human comment
reading Changes requested: <reason>. Omit --reason and it records
Changes requested.
Both verbs are durable operations: replaying the same decision returns the same
result rather than acting twice, and a conflicting second decision is refused
with a message like This human gate already has a durable approval decision.
See tde task for the full grammar.
Deciding from your phone
Two different Telegram flows reach a decision, and they are not the same thing.
Parked questions. When an agent emits an ASK: block, TDE mirrors it to
Telegram with one inline button per real option — the button labels are the
question's own option labels, never guessed from prose. Tapping one records the
answer, unparks the task and re-runs the stage with your answer in context.
Answering twice reports Already handled.
Guard approvals. When a guarded action needs a decision, the message reads:
🔐 Approval needed
run: rm -rf ./build
Guard rule: rm -rf (recursive delete)with two buttons, ✅ Approve and ❌ Deny. The desktop shows the same modal;
whichever surface answers first wins, and the other reports Already handled.
The bot also answers three commands:
/tasks — list current board tasks by stage
/status — task counts per stage
/help — this messageAnything else you send is treated as a new task. See Slack and Telegram.
Parked questions in detail
An agent that hits a genuine decision — a real choice with real consequences, not a guess — is instructed to stop and ask rather than pick. The block it emits looks like this:
ASK: Which database should this service use?
HEADER: Database choice
MULTI
OPTION: Postgres :: managed, strong consistency
OPTION: SQLite :: zero-ops, single node
INPUT: or name another
SECRETMULTI allows more than one selection, INPUT allows a typed free-text answer,
and SECRET masks that typed answer. With no options at all, free text is
implied.
TDE parks the task, stores the question, logs awaiting human input: … and
raises an Activities event titled <task> needs your input. In the Activities
detail, the footer buttons are the question's real options, followed by Submit
for a multi-select and ✏ Type answer when free text is allowed.
Answering writes a comment — Human answer to: <question> plus your selection
and input — clears the parked question and re-runs the stage. If a newer question
arrived in the meantime, the older answer is refused with
A newer question is waiting. Review it before answering.
The CLI can answer the same question: tde task approve ID --response TEXT
matches your text against the option ids and labels, case-insensitively, and
falls back to free text only when the question allows it.
Guards
Guards are the policy layer around risky actions, configured in Control → Guards. Each guard is a toggleable rule that matches either a shell command the agent wants to run or a file path it wants to write.
Destructive rules ship enabled; the rest are opt-in:
| Rule | Category | Default |
|---|---|---|
rm -rf / (wipe root) | Filesystem | on |
rm -rf ~ (wipe home) | Filesystem | on |
mkfs (format filesystem) | Filesystem | on |
dd if=… (raw disk write) | Filesystem | on |
| read SSH private keys | Secrets & credentials | on |
write into ~/.ssh | Secrets & credentials | on |
rm -rf (recursive delete) | Filesystem | off |
sudo, chmod 777 | Privilege & permissions | off |
curl|wget piped to a shell, wget | Network | off |
write a .env file | Secrets & credentials | off |
git push --force, git reset --hard, git clean -fd | Version control | off |
npm publish, cargo publish | Package publishing | off |
The same panel carries Human gate: (on by default), Max diff: (off, 200, 500
or 800 lines) and Smart effort:. You can add your own rules in
label | command form. Everything persists to ~/.tde/pipeline.json, and newly
shipped built-ins are merged in without clobbering your toggles.
A guard violation found at the Review stage fails the verdict — the task goes
back to Build with a reason like blocked command (git push --force) used,
blocked write (write into ~/.ssh) to … or
diff too large: 940 lines > 800 limit.
Why an agent cannot approve its own work
This is enforced structurally, in three independent places.
The tool surface has no approve verb. The Control MCP server exposes exactly
runtime_status, task_create, task_list, task_show, task_run,
task_watch, task_logs, task_cancel, task_retry, task_block,
task_unblock, task_comment, project_list, pipeline_list, agent_list and
share_status. There is no task_approve and no task_reject. Approval exists
only as a command on the deterministic CLI over the owner-only socket, and in the
UI.
The agent is told so. The chat agent's system prompt states that approval, rejection and share lifecycle are human-only actions and that it must ask you to use the CLI or the UI — and that it must never claim an action succeeded unless a tool actually returned success.
Request-but-not-grant flows exist where they are needed. An agent can
decision_propose a decision record, which is never auto-accepted, and
decision_request_approval, which only links that proposal to a task so the
human Loop gates it. There is no decision_accept tool; accepting is a click in
the Decisions panel. The Slack plan gate works the same way: only the explicit
approve option unlocks it, and any other answer leaves the task blocked rather
than resuming a state-changing request.