Skip to content

PendingToolInterrupt

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:212

A single tool call a human-in-the-loop interrupt is waiting on, surfaced from the suspended graph state so a consumer (the interactive session) can render an approve/reject prompt. Mirrors LangChain’s HITL ActionRequest (tool name + the args it would run with).

args: Record<string, unknown>

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:214


optional denyPreview?: string

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:323

[[TUI-C26]] §6 — what the menu’s always reject choice will record, in the object form the user would write in a config file. The deny mirror of grantPreview, shown at the moment of the choice for the same reason: a control that does not say what it stores is one the user has to guess at.

Its availability is NOT the grant’s, and reading one off the other is the mistake this field exists to prevent. The matcher’s own rule is undecidable → no match on the allow side, a match on the deny side, so a command the gate cannot statically resolve can be refused permanently even though it can never be allowed permanently. always reject also stays offered on a catastrophic verdict, where §4.2 withdraws every sticky grant: sticky refusal is safe in every direction, and it is only the grant that is ever made harder.

Absent only where the entry grammar cannot hold one at all — today, an MCP call whose server could not be attributed (the grammar’s server cannot be empty). Absent means the control is not offered, never offered-and-disabled.


optional denySummary?: string

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:329

§6 — the same deny entry in the words the control is written in, through the one-liner every other provenance message uses. Present exactly when denyPreview is, since both are rendered from the one entry the runner would record.


optional escalatedBy?: string

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:272

EXT-71 §3.2 — when this call reached the human because a declared approvals.escalate entry matched it, the entry that fired, rendered for display. It is the provenance the prompt shows: an escalation the user cannot trace to the line they wrote reads as the gate malfunctioning. Absent whenever the escalation came from the rung or the rater instead.


optional grantPreview?: string

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:290

EXT-71 §6 — what a sticky choice will store, rendered in the object form the user would write in a config file, e.g. { "type": "shell", "matcher": "exact", "pattern": "npm test" }. The menu MUST show this at the moment of the choice, on every surface: the user is shown the thing they are agreeing to rather than a generalization of it, which is what makes the display honest and cheap at once.

For a tool call the stored thing is the tool, not the arguments (§4.7.4), so this reads e.g. { "type": "mcpTool", "server": "fetcher", "matcher": "exact", "pattern": "fetch_url", "host": "docs.internal.example" }. That is the one place a grant is deliberately broader than what the human was shown, which is why the display carries the most weight there.

Absent exactly where no sticky grant is on offer — a catastrophic outcome (§4.2 withdraws the persistent grants), a rung that remembers nothing, a command that does not statically resolve, or a tool call naming more than one host (which has no honest single-host entry) — so a prompt never advertises a control that has already been withdrawn.


optional grantSummary?: string

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:305

§6 — the same grant in the words a menu control is written in: npm test for a shell command, tool gth_web_fetch (host docs.internal.example) or mcpTool jira/create_issue for a tool call. It is what the always approve control names, so the control reads as always approve this tool for this host rather than as a bare key.

It is rendered by describeApprovalEntry — the one-liner every other provenance message uses, including the §4.7.4 notice that later withdraws the grant. Sharing the renderer is the point: a menu that describes a grant differently from the notice that withdraws it is how a user stops trusting either.

Present exactly when grantPreview is, since both are rendered from the one entry recordApproval would write.


optional id?: string

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:231

[[TUI-C69]] §5.4 — the LangChain tool_call id this interrupt is holding, so a decision about the call can be attributed to the call on screen.

LangChain’s HITL ActionRequest is { name, args, description } and carries no id, so the suspended-state reader recovers it from the AI message the requests were built from: the same getState snapshot that holds tasks[].interrupts also holds values.messages, whose last AIMessage carries the very tool_calls the middleware filtered. Matched back by name and arguments within that one message, in order, so it is the call’s own id rather than a correlation.

It is a display attribution, never a decision input. Nothing about whether a call is allowed may read it: the gate decides on the command and the subject, and an id is neither. Optional because the recovery is defensive at every step — an unexpected state shape yields an interrupt with no id, and every consumer of the id treats its absence as “attribute nothing”.


name: string

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:213


optional negotiationAttempts?: number

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:361

[[EXT-29]] §5.3 — how many attempts the agent actually made, counted since a human was last involved rather than since the last approved call.

Persistence is the single most decision-relevant fact this block carries, and it is measured against a real under-report: on the escalation this comes from, the agent proposed the same command five times, was refused every time, and the human was shown three, because the two approved calls it made in between (git stash, git status) each erased the rounds before them. [[EXT-108]] removed that erasure, so the array and this count are now the same set as they leave the state machine.

It stays a separate number for the reason that survives: both values are handed over whole and the renderer is what slices for the screen, so sending both is what lets it number the rounds it prints by their true attempt number instead of from one.

Present exactly when negotiationRounds is, and never smaller than its length.


optional negotiationRounds?: readonly RaterNegotiationRound[]

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:343

[[EXT-29]] §6 — every round of the §5 negotiation that preceded this escalation, oldest first, when one did.

The user is not asked to rule on the final command in isolation: that the agent proposed git reset --hard origin/main three times unchanged, against two rejections that each told it what to fix, is itself the most important thing on the screen, and it is invisible if only the last attempt is shown. core/shell/negotiation.ts’s renderNegotiationTranscript is the shared renderer, so two surfaces cannot describe one exchange two ways.

Absent for every escalation that had no negotiation — catastrophic (§4.2 gives it no rounds at all), a declared approvals.escalate entry, an unrated rung, a tool subject.


optional safetyVerdict?: object

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:265

CFG-26 — when the AI rater escalated this run_shell_command to the human (rather than approving it or bouncing it back to the model), the rater’s verdict is attached here so the approval surface can show an “AI rater (): ” notice. Absent when the rater is off, or when the command reached the human without being rated.

outcome: "safe" | "destructive" | "catastrophic" | "attack"

reason: string

optional suggestedTool?: string


optional subject?: ApprovalSubject

Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/types.ts:258

[[TUI-C67]] — what kind of call this is, as the gate itself decided it (§3.1/§4.7.5): a shell subject carrying the command, a tool subject, or an mcpTool subject carrying the user’s own mcpServers key. It is the discriminator decideToolApprovalInner matched rules on, travelling to the surface so the prompt can branch on the same one.

Both terminal approval surfaces used to open with the agent wants to run a shell command, which EXT-80 made false for the most common prompt in manual and write — a file write, an MCP call or a custom tool, each announced as a shell command. core/approvals/promptHeader.ts renders the sentence from this field for both of them; re-deriving the kind from name on each surface would be a second classifier, free to disagree with the one that gated the call.

The ACP server is currently that second classifier, and the rule above still stands. acpPermissions.ts titles its permission request from its own shellCommandOf, keyed on name rather than on this field, which it never reads — a divergence pinned by an assertion in packages/agent/spec/acpServer.spec.ts. Closing it means reading this field there rather than the tool name, and that is [[TUI-C89]]’s work; do not read the divergence’s existence as licence to re-derive the kind anywhere else.

The runner attaches it to every interrupt it hands the approval callback, including the plainest case where nothing else on this interface is set. Optional only because an interrupt read back out of graph state (getPendingToolInterrupts) is assembled before any subject exists; a surface that renders through promptHeader and is handed one of those renders the generic tool sentence, never a wrong one.