Skip to main content

Interface: HumanGate

Defined in: internal/human-gate/HumanGate.ts:70

HumanGate -- the mechanics of a run's pause: store it, hand the card to the UI, let one decision through

About​

The policy decides that a human is needed; the gate makes the wait safe.

Implements IHumanGate over the RunStore. Three pause kinds (EPauseKind): a tool call waiting for approval, the final answer waiting for a reviewer (D99), the model's question to the user (D62). Every pause is a list of envelopes, one per card the UI renders (D102), stamped with the run, a fresh pauseId and one expiresAt.

  • pause stores the run as PAUSED with its envelopes.
  • assertResumable and matchDecisions check the decisions against the envelopes, without I/O.
  • resume and cancel move the run out of PAUSED in one atomic step (D69): of two callers, one wins, the other reads a 409.
  • expirePaused cancels the pauses nobody answered within timeoutMs (D75, D80).

The decision itself is applied elsewhere: the LLMClient resumes the loop with it, the settlement delivers or drops a reviewed answer.

Who needs it​

settleRun and settleReviewPause (pause); AIGateway.resume (assertResumable, matchDecisions, resume), cancelRun, listPausedRuns, expirePausedRuns. A host replaces it with its own IHumanGate in overrides.humanGate.

What needs​

runStore, whose transition is the atomic step.

Usage​

When to use it​

Always, through the gateway: createGatewayDeps creates it from IAIGatewayConfig.humanGate.

When shouldn't use it​

To decide whether a call needs a human: that is ToolPolicy.decide. To render the card: the app does, from the envelope's data.

Pitfalls​

A decision carries the pauseId of the card it answers: a run pauses again under the same runId (a tool, then its answer's review), and a stale decision is a 409. The gate does not close the thread or the tool calls of a cancelled run; the gateway does, with the envelopes the gate returns.

Implements​

Methods​

assertResumable()​

assertResumable(__namedParameters): void

Defined in: internal/human-gate/HumanGate.ts:216

assertResumable -- the checks of resume on a run already loaded, without I/O

About​

Refuse early what resume would refuse anyway.

Throws what resume throws for a run that cannot take these decisions: 409 when the run is not paused or pauseId names another pause of it, 410 when the pause has expired, 400 when the decisions do not answer the pause — every envelope needs exactly one decision, named by the envelope's callId (a single-envelope pause takes one decision without it), with an action among that envelope's allowed. Returns nothing when the decisions may go through; matchDecisions gives the pairs.

AIGateway.resume calls it first, so a resume that is doomed pays no judge call for the answer screen (D71) and no repository reads; resume calls it again on the run it reloads, right before the atomic transition.

Pitfalls​

It does not reserve anything: another caller can still win the transition in resume.

Parameters​

__namedParameters​
decisions​

IResumeDecision[]

pauseId​

string

run​

IRun

Returns​

void

Implementation of​

IHumanGate.assertResumable


cancel()​

cancel(__namedParameters): Promise<void>

Defined in: internal/human-gate/HumanGate.ts:422

cancel -- closes a paused run the user gave up on

About​

PAUSED to CANCELLED, atomically; a run that is not paused is a 409.

Who needs it​

AIGateway.cancelRun, before closeRun rejects the pending tool calls.

Parameters​

__namedParameters​
runId​

string

tenantId​

string

Returns​

Promise<void>

Implementation of​

IHumanGate.cancel


expirePaused()​

expirePaused(__namedParameters): Promise<IRun[]>

Defined in: internal/human-gate/HumanGate.ts:474

expirePaused -- cancels the pauses nobody answered in time

About​

Every paused run of the tenant whose expiresAt has passed becomes CANCELLED; the cancelled runs come back, with the envelopes of their pause.

A PAUSED run without an envelope counts as due too (D80): nothing can resume it (assertResumable is a 409 without a pending envelope), so it would hold its thread for ever; the job is the only way out of that state. The envelopes of one pause share their expiresAt, so the first one speaks for all. Each run leaves PAUSED through the atomic transition (D69), so a resume racing with the job loses cleanly. The gate only moves the runs: closing their tool calls in the thread and reopening the thread is the gateway's part (AIGateway.expirePausedRuns), which needs the runs, hence the return value (D75). The transition clears pending, so each returned run carries the envelopes of its pause again: the gateway closes every call with its own rejection text (D87).

Who needs it​

AIGateway.expirePausedRuns, called by a scheduled job of the application.

Parameters​

__namedParameters​
tenantId​

string

Returns​

Promise<IRun[]>

Implementation of​

IHumanGate.expirePaused


listPaused()​

listPaused(__namedParameters): Promise<IRun[]>

Defined in: internal/human-gate/HumanGate.ts:396

listPaused -- the paused runs of a tenant

About​

The inbox, as the store has it.

RunStore.listPaused: the tenant's PAUSED runs, one user's when userId is given.

Who needs it​

AIGateway.listPausedRuns, which strips the reviewer's draft before the asker sees the list (D111).

Parameters​

__namedParameters​
tenantId​

string

userId?​

string

Returns​

Promise<IRun[]>

Implementation of​

IHumanGate.listPaused


matchDecisions()​

matchDecisions(__namedParameters): object[]

Defined in: internal/human-gate/HumanGate.ts:250

matchDecisions -- pairs every decision of a resume with the envelope it answers

About​

Which card does this answer belong to.

The pairs in the order of the envelopes. Throws what assertResumable throws: 409 when the run is not paused or pauseId is not the pause it waits on (a decision meant for an earlier pause of the same run), 410 when the pause has expired, 400 when a decision names no envelope of the pause (callId), an envelope gets no decision or two, or an action is not among its envelope's allowed. A pause of one envelope takes a single decision without callId.

Who needs it​

assertResumable, and AIGateway.resume to read each decision next to its envelope (the answer screen, the rejection texts).

Parameters​

__namedParameters​
decisions​

IResumeDecision[]

pauseId​

string

The pause the decisions are for (envelope.pauseId).

run​

IRun

Returns​

object[]

Implementation of​

IHumanGate.matchDecisions


pause()​

pause(__namedParameters): Promise<TPauseEnvelope[]>

Defined in: internal/human-gate/HumanGate.ts:93

pause -- stores the run as PAUSED with the cards of its pause

About​

The run stops here; these are the cards the UI shows.

Stamps every draft with the run's id, one fresh pauseId and one expiresAt (now + timeoutMs), saves the run as PAUSED with them as pending and returns them. One envelope per tool call of the step that waits for a human (D102), or the one envelope of a result review (D99). An empty list is a 500 (error 5376634521017): a pause without a card could never be answered.

Who needs it​

settleRun, when the loop stopped on tool calls that need a human; settleReviewPause, when the answer waits for a reviewer.

Parameters​

__namedParameters​
envelopes​

(Omit<IToolApprovalEnvelope, "runId" | "pauseId" | "expiresAt"> | Omit<IResultReviewEnvelope, "runId" | "pauseId" | "expiresAt"> | Omit<IUserQuestionEnvelope, "runId" | "pauseId" | "expiresAt">)[]

run​

IRun

tenantId​

string

Returns​

Promise<TPauseEnvelope[]>

Implementation of​

IHumanGate.pause


resume()​

resume(__namedParameters): Promise<IRun>

Defined in: internal/human-gate/HumanGate.ts:155

resume -- lets the decisions through and marks the run RUNNING

About​

One pause, one answer: a decision per envelope, and the first caller wins, every other one is refused.

Validates the decisions (assertResumable), then moves the run PAUSED to RUNNING in one atomic step (D69) and returns it. The LLMClient applies the decisions (run.resume).

  • 409 when the run is not paused, when pauseId is not the pause the run waits on, or another caller resumed or cancelled it first.
  • 410 when the pause has expired.
  • 400 when a decision names no envelope, an envelope gets no decision or two, or an action is not allowed on its envelope.

Who needs it​

AIGateway.resume, after the profile, tool and answer checks, before the LLMClient continues the run.

Pitfalls​

Two resumes at once (a double click, two tabs) reach this method together; the atomic transition admits one, so a side-effect tool never runs twice for one approval.

Parameters​

__namedParameters​
decisions​

IResumeDecision[]

pauseId​

string

The pause the decisions are for (envelope.pauseId).

runId​

string

tenantId​

string

Returns​

Promise<IRun>

Implementation of​

IHumanGate.resume