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.
pausestores the run as PAUSED with its envelopes.assertResumableandmatchDecisionscheck the decisions against the envelopes, without I/O.resumeandcancelmove the run out of PAUSED in one atomic step (D69): of two callers, one wins, the other reads a 409.expirePausedcancels the pauses nobody answered withintimeoutMs(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
resumewould 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
pauseId
string
run
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
expiresAthas 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
pauseId
string
The pause the decisions are for (envelope.pauseId).
run
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
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
pauseIdis 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
pauseId
string
The pause the decisions are for (envelope.pauseId).
runId
string
tenantId
string
Returns
Promise<IRun>
Implementation of
IHumanGate.resume