Graph
Class: Graph
Defined in: src/core-flow/Graph.ts:402
A fixed DAG of runners. Build one with graph.
Extends
Constructors
Constructor
new Graph(
opts):Graph
Defined in: src/core-flow/Graph.ts:429
Parameters
opts
Returns
Graph
Overrides
Properties
enable
readonlyenable:EnableNamespace
Defined in: src/core/RunnerBase.ts:729
Enable-namespace for high-level observability features. Each method
attaches a pre-built CombinedRecorder and returns an unsubscribe
function. Consumers write ONE line to enable rich observability,
instead of N .on() subscriptions.
Inherited from
id
readonlyid:string
Defined in: src/core-flow/Graph.ts:404
name
readonlyname:string
Defined in: src/core-flow/Graph.ts:403
Methods
attach()
attach(
recorder):Unsubscribe
Defined in: src/core/RunnerBase.ts:554
Attach a footprintjs CombinedRecorder to observe every subsequent run.
LIFECYCLE CONTRACT (who owns cleanup):
- Attached recorders live for the RUNNER's lifetime, not a run's.
NOTHING auto-expires per-run — a recorder attached once observes
every later
run()until you call the returned Unsubscribe. - The CALLER owns cleanup. Keep the Unsubscribe and call it when the observer's life ends (request scope, UI unmount, test teardown).
- Event listeners (
on()/once()) follow the same rule, with two extra outs: pass{ signal }for AbortSignal auto-cleanup, or callremoveAllListeners()to bulk-drop listeners (listeners ONLY — recorders are not affected). once()listeners are the only self-expiring subscription.
attach() is NOT idempotent: every call pushes another entry. (At run time footprintjs's executor dedupes recorders by ID, so same-ID duplicates won't double-fire — but the runner-side array still grows.) Attaching in a per-run loop without detaching is the classic server leak; attach once, or detach per-run.
WHEN it starts observing: the NEXT run. Recorders are handed to the
executor when the executor is built, at run start, so one attached WHILE
a run is in flight sees nothing of that run and everything of the one
after — it is not dropped, it is early. Between runs (or before the
first) is the ordinary case and works exactly as it reads. Event
listeners are the opposite: on() takes effect immediately, but only for
events emitted after it, so a listener added mid-run sees the rest of
that run and none of its beginning.
Parameters
recorder
Returns
Unsubscribe
Inherited from
closeToolSessions()
closeToolSessions(
options?):Promise<number>
Defined in: src/core/RunnerBase.ts:722
End the tool sessions held for one hosting session.
The mechanism is the library's; the TIMING is yours. Nothing in this
package can know when a request/reply session is over — a HostRequest
carries a sessionId and no end, SessionLifecycle is hydrate/persist
by design (a TTL, a scan or a delete is the STORE's own API, not a demand
this port makes of every store that will ever implement it), and AWS itself
does not tell you: an idle timeout is the reality. Guessing a boundary here
would tear down a live sandbox mid-conversation.
So the composition root, which already owns the shape of the process, says
when — the same doctrine that stops shutdownOn from grabbing signals by
default. On the conversation door that is one line:
conversation.onClose(() => void agent.closeToolSessions({ sessionId }));A request/reply deployment that knows its own boundary — a logout, a job finishing, a cart abandoned — calls the same method.
Never calling it is survivable, not silent: sessions idle out on the tier's
lazy sweep, a bounded live count evicts the coldest, and shutdown() takes
whatever is left.
Parameters
options?
reason?
sessionId?
string
Returns
Promise<number>
how many cleanups ran. 0 when this runner holds none — a
composition, or an agent whose tools never opened anything.
Example
host.onSessionEnd(async (sessionId) => {
const closed = await agent.closeToolSessions({ sessionId });
log.info({ sessionId, closed }, 'tool sessions released');
});Inherited from
emit()
emit(
name,payload):void
Defined in: src/core/RunnerBase.ts:777
Emit a consumer-defined custom event.
If name matches a registered event type, this routes exactly like a
library-emitted event (via the typed EventMap). Otherwise it flows
through to wildcard listeners ('*') as an opaque CustomEvent with
minimal meta. Library events remain reserved under agentfootprint.*.
Parameters
name
string
payload
Record<string, unknown>
Returns
void
Inherited from
getCommitCount()
getCommitCount():
number
Defined in: src/core/RunnerBase.ts:159
How many commits the run has written so far — footprintjs's
executor.getCommitCount(), forwarded.
This is the run's TIME AXIS. One commit lands per executed stage, in
order, so the count sampled at some moment is that moment's position
in the run. Observers stamp it to say WHEN they fired: it is what
boundaryRecorder({ getCommitCount }) records on every boundary, and
the only reason a step strip can be rebuilt from a stored recording
later. Sample it live, at the moment of the event — a number read
once and captured is a number about the wrong instant.
0 before the first run, and during a run it climbs; between runs it
is the last run's total. Cumulative across resume() on the same
executor, and it counts the whole run — a subflow's own commits are
kept out of the run-level log by footprintjs, so this is the parent
timeline, not a sum of every nested one.
Returns
number
Inherited from
getLastSnapshot()
getLastSnapshot():
RuntimeSnapshot|undefined
Defined in: src/core/RunnerBase.ts:122
Returns the footprintjs snapshot from the most recent run. The snapshot is the CANONICAL STRUCTURE: nodes, edges, executionTree, runtimeStageId, commitLog.
Domain consumers (Lens, Trace, dashboards) read this for shape
and join their own per-stage payload by runtimeStageId. They
MUST NOT re-derive structure from typed events — that's the
design footprintjs's CLAUDE.md Convention 1 explicitly forbids.
undefined before the first run() has STARTED. After that it is the
most recent run's snapshot, including across multi-turn reuse of the same
runner instance.
Live during a run. The executor is assigned at run start, so a caller reading this from inside a run — an event listener, a tool, a recorder — gets the IN-FLIGHT snapshot, partially filled, not the last completed one. RunnerBase.getSnapshot is the same value under the name that says so. Anything that must describe a FINISHED run has to capture at the terminal flush instead of polling this.
Returns
RuntimeSnapshot | undefined
Inherited from
getLevels()
getLevels(): readonly readonly
string[][]
Defined in: src/core-flow/Graph.ts:448
How the graph was levelized — level 0 first. Stable post-construction.
Returns
readonly readonly string[][]
getSnapshot()
getSnapshot():
RuntimeSnapshot|undefined
Defined in: src/core/RunnerBase.ts:137
Alias for getLastSnapshot() that mirrors FlowChartExecutor.getSnapshot()
so consumers (lens, playground, ExplainableShell) can read the live or
just-completed snapshot through the same method name they'd use on a
footprintjs executor — without having to know whether they're holding
an agentfootprint Runner or a raw executor.
During an active run, returns the live snapshot (commit log + execution tree built incrementally as stages execute). Between runs, returns the last completed run's snapshot. Undefined before any run has started.
Returns
RuntimeSnapshot | undefined
Inherited from
getSpec()
getSpec():
FlowChart
Defined in: src/core/RunnerBase.ts:182
Return the footprintjs FlowChart for this runner — the canonical
design-time blueprint. STABLE REFERENCE across calls (getSpec() === getSpec()). Set once at construction via initChart().
Pairs with the run-time getters (getLastSnapshot,
getCommitCount) and matches ExplainableShell.spec +
specToReactFlow(spec, ...) consumer conventions. Its
buildTimeStructure field is what a viewer draws — save it with the
snapshot when storing a run, since no snapshot carries it.
DO NOT OVERRIDE in subclasses — the reference-identity contract
(Lens / OpenAPI / MCP caches memo on this returning the same
object) depends on the inherited body returning this.chart
directly. To customise build behaviour, override buildChart()
instead; this getter must remain a thin cache-read.
Returns
FlowChart
Inherited from
getUIGroup()
getUIGroup<
T>():T|undefined
Defined in: src/core/RunnerBase.ts:218
Return the consumer-shaped UI group for this composition — produced
by invoking the consumer's groupTranslator (if attached) with this
runner's GroupMetadata. Returns undefined when no translator was
attached.
STABLE REFERENCE across calls. Computed on first access and cached;
subsequent calls return the same value. Pairs with getSpec() —
library shape on one side, consumer-shaped UI on the other.
Subclasses MUST override buildUIGroupMetadata() (the next hook) to
supply the GroupMetadata for their composition kind. This method
(the public surface) is final-by-convention — do not override.
Type Parameters
T
T = unknown
Returns
T | undefined
Inherited from
getUIGroupWith()
getUIGroupWith<
T>(override):T|undefined
Defined in: src/core/RunnerBase.ts:262
Translate this runner's group metadata with a CALLER-SUPPLIED
translator that overrides the runner's own default. Used by
parent compositions to apply per-method translator overrides.
See the Runner.getUIGroupWith JSDoc for the contract.
Type Parameters
T
T = unknown
Parameters
override
GroupTranslator<unknown>
Returns
T | undefined
Inherited from
listenerCount()
listenerCount(
type?):number
Defined in: src/core/RunnerBase.ts:518
Diagnostic — how many event listeners this runner currently retains.
No argument = total across all buckets (the leak-detection number);
with a subscription key = that bucket only. Delegates to
EventDispatcher.listenerCount().
Parameters
type?
keyof AgentfootprintEventMap | WildcardSubscription
Returns
number
Inherited from
off()
Call Signature
off<
K>(type,listener):void
Defined in: src/core/RunnerBase.ts:461
Unsubscribe a previously-registered listener.
Type Parameters
K
K extends keyof AgentfootprintEventMap
Parameters
type
K
listener
EventListener<K>
Returns
void
Inherited from
Call Signature
off(
type,listener):void
Defined in: src/core/RunnerBase.ts:462
Parameters
type
WildcardSubscription
listener
WildcardListener
Returns
void
Inherited from
on()
Call Signature
on<
K>(type,listener,options?):Unsubscribe
Defined in: src/core/RunnerBase.ts:438
Subscribe a typed listener. Returns unsubscribe.
Lifecycle: the subscription lives until you call the returned
Unsubscribe, the { signal } you passed aborts, or
removeAllListeners() runs. Nothing auto-expires per-run — pass a
per-run AbortSignal for request-scoped listeners on long-lived
runners (servers).
Type Parameters
K
K extends keyof AgentfootprintEventMap
Parameters
type
K
listener
EventListener<K>
options?
ListenOptions
Returns
Unsubscribe
Inherited from
Call Signature
on(
type,listener,options?):Unsubscribe
Defined in: src/core/RunnerBase.ts:443
Subscribe to a domain wildcard (e.g. 'agentfootprint.context.') or ''.
Parameters
type
WildcardSubscription
listener
WildcardListener
options?
ListenOptions
Returns
Unsubscribe
Inherited from
once()
Call Signature
once<
K>(type,listener,options?):Unsubscribe
Defined in: src/core/RunnerBase.ts:472
Subscribe a one-shot listener (fires once then auto-removes). Accepts { signal }.
Type Parameters
K
K extends keyof AgentfootprintEventMap
Parameters
type
K
listener
EventListener<K>
options?
Omit<ListenOptions, "once">
Returns
Unsubscribe
Inherited from
Call Signature
once(
type,listener,options?):Unsubscribe
Defined in: src/core/RunnerBase.ts:477
Parameters
type
WildcardSubscription
listener
WildcardListener
options?
Omit<ListenOptions, "once">
Returns
Unsubscribe
Inherited from
removeAllListeners()
removeAllListeners():
void
Defined in: src/core/RunnerBase.ts:508
Lifecycle escape hatch — drop EVERY event listener on this runner in
one call (typed, domain-wildcard, and '*'). Delegates to
EventDispatcher.removeAllListeners().
For long-lived runners on servers: when you can't thread an
AbortSignal or keep every Unsubscribe handle, call this between
requests to guarantee zero residual subscriptions. Note it also
removes listeners wired by enable.* strategies — re-enable after
calling if you still want them. Does NOT touch attached recorders
(see attach() — recorders have their own Unsubscribe).
Returns
void
Inherited from
resume()
resume(
checkpoint,input?,options?):Promise<RunnerPauseOutcome|GraphOutput>
Defined in: src/core-flow/Graph.ts:464
Resume a paused run from its checkpoint. Default behavior: rebuild the
chart, wire the same core recorders + consumer recorders, call
executor.resume(checkpoint, input), and emit pause.resume before
returning. Subclass overrides only if it needs specialized behavior.
Parameters
checkpoint
FlowchartCheckpoint
input?
unknown
options?
RunOptions
Returns
Promise<RunnerPauseOutcome | GraphOutput>
Overrides
run()
run(
input,options?):Promise<RunnerPauseOutcome|GraphOutput>
Defined in: src/core-flow/Graph.ts:452
Execute the runner. Subclass may override for specialized input mapping, but default invokes getSpec() + FlowChartExecutor.
Parameters
input
options?
RunOptions
Returns
Promise<RunnerPauseOutcome | GraphOutput>
Overrides
shutdown()
shutdown(
options?):Promise<void>
Defined in: src/core/RunnerBase.ts:639
Drain and release what was enabled on this runner.
The agent itself remains usable afterwards; shutdown() drains and
releases what was enabled on it. Nothing about the runner is destroyed:
run() still works, listeners still fire, and enabling telemetry again
gives you a fresh, live handle.
The order is the part worth having in one place:
- every handle FLUSHES first — including the events still queued on a
detachdriver, which have not reached the strategy yet; - only then does anything stop, so a strategy shared by two handles is fully drained before either releases it;
- a strategy is stopped only once nothing is still subscribed to it,
and at most once ever (see
strategies/lifecycle.ts).
Parameters
options?
stop?
boolean
Default true. Pass false to drain WITHOUT
releasing — what a host does when it is shutting down but does not own
the agent it was handed (standingAgent's default shutdown: 'flush').
Returns
Promise<void>
Example
Graceful exit for a script
const telemetry = agent.enable.observability({ strategy: cloudwatch });
const answer = await agent.run({ message: 'hi' });
await agent.shutdown();