@agent-forge/plugin-decision-escalation
Decision escalation capability plugin for Agent Forge: a formal ask channel
for forks — the model asks with decision_ask, the user answers on a local
confirmation page or in TUI text, and an unattended fork never stalls
forever: reversible levels are adjudicated by an independent subagent chain
on timeout, irreversible ones stay stopped.
- Design contract (authoritative):
docs/design/决策升级插件设计.md - Position: a decision channel alongside the goal/plan/todo layers; the main agent keeps L0 (trivial fork) autonomy — this plugin only takes over when a fork is worth a formal ask.
Levels (设计 §3)
| Level | Verdict | On timeout |
|---|---|---|
| L0 | reversible, local impact | not this plugin's business |
L1 (routine) | reversible, single module | one independent adjudicator agent |
L2 (significant) | reversible but directional | one advocate per reversible option + independent judge |
| L3 (redline) | any option reversibility: "irreversible" or field missing | never adjudicated — stays stopped, fork written back to the user |
The redline is engine-derived from per-option reversibility, never the
model's risk self-assessment; risk_class only picks L1 vs L2 handling.
Tools
| Tool | Purpose |
|---|---|
decision_ask | Ask a fork (2..4 closed options + symmetric context_digest brief + risk_class). Returns decisionId, page URL, timeoutMs, riskClass, redLine immediately; non-blocking. |
decision_resolve | Book the answer: source: "user" (page or TUI text) or source: "adjudication" — the latter only when an injected verdict exists and matches; fabricating one is rejected. |
Command: /decision log [N] · /decision show <id> · /decision cancel <id>.
Timeout de-escalation (设计 §5)
Factory default 180s (config.timeoutMs overrides). On timeout without a
user answer: redline forks inject a decision.notice halted notice; L1/L2
forks dispatch the adjudication chain through the public
agent-forge.agent-task-scheduler@1 capability (subagent-delegate),
readonly profile with read/grep/ls/find only. Every chain failure —
capability missing, submit/drain error, subagent not succeeded, output
violating the {"choice","confidence","rationale","keyEvidence"} JSON
protocol — lands as adjudication.unavailable / adjudication.unparseable
and the same halted behavior: stop, no retry, no silent downgrade.
Persistence protocol
Structurally identical to the goal/plan/alignment plugins: append-only
decision.state full snapshots plus one decision.transition audit entry
per conversion (asked | answered.user | answered.adjudication | timed-out.halted | timed-out.adjudicating | adjudication.unavailable | adjudication.unparseable | cancelled), announced as decision.changed.
The full adjudication verdict rides the decision.state snapshot for
/decision show. User answers inject as decision.answered
(display: false, model context); notices inject as decision.notice
(display: true, user visible).
Notes
- Requires a host session that exposes
SessionAPI.sendMessage(answer and notice injection); without itdecision_askfails fast and the rest of the surface stays usable. - The confirmation page is one of two answer channels for a present user; unattended sessions never depend on it.
- Deterministic tests only (fake CapabilityAPI + fake scheduler handle +
real
node:httpon an OS-assigned port); no real model calls. Adjudication quality is a model quality attribute and deliberately untested (设计 §9).