agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
106 lines • 5.61 kB
TypeScript
/**
* eventMeta — build EventMeta from a footprintjs TraversalContext.
*
* Pattern: Adapter (GoF) — translates footprintjs's per-stage context into
* agentfootprint's per-event metadata shape.
* Role: Used by every core recorder to attach meta to emitted events.
* Emits: N/A (helper only).
*/
import type { TraversalContext } from 'footprintjs';
import type { EventMeta, EventSourcePosition } from '../events/types.js';
/**
* Minimal "stage origin" shape — the common subset of footprintjs's
* TraversalContext (from FlowRecorder events), RecorderContext (from
* Recorder data-flow events), and EmitEvent (from EmitRecorder).
*
* Note: `subflowPath` type varies across footprintjs event shapes:
* - TraversalContext.subflowPath → `string | undefined` (/-separated)
* - EmitEvent.subflowPath → `readonly string[]`
* - RecorderContext → N/A (derived from runtimeStageId)
*
* We accept both shapes and normalize inside `buildEventMeta`.
*/
export interface StageOrigin {
readonly runtimeStageId?: string;
readonly subflowPath?: string | readonly string[];
/** Source emission time, when this origin is an emit rather than a flow hook. */
readonly timestamp?: number;
/** Already validated wire coordinates; never read from live executor state. */
readonly sourcePosition?: EventSourcePosition;
}
export interface RunContext {
/** Millisecond wall-clock timestamp when the run started. */
readonly runStartMs: number;
/** Unique run id (demultiplex concurrent runs sharing one dispatcher). */
readonly runId: string;
/** Optional OTEL trace id forwarded from executor.run({ env: { traceId } }). */
readonly traceId?: string;
/** Optional correlation id for cross-event tying (retrieval→injection→LLM). */
readonly correlationId?: string;
/** Composition ancestry path (e.g. ['Sequence:bot', 'Agent:classify']). */
readonly compositionPath: readonly string[];
/** Optional turn/iter indices from agent runtime. */
readonly turnIndex?: number;
readonly iterIndex?: number;
/** The hosting conversation this run belongs to, when it belongs to one
* (9.4.0). Absent for an unhosted or anonymous run — never fabricated. */
readonly sessionId?: string;
/** WHO the caller NAMED for this run (9.11.0) — from an explicit
* `run({ identity })` only, never from the synthesized `runIdentity` and
* never derived from a session. Absent for an anonymous run. */
readonly principal?: string;
/** The tenant the caller NAMED for this run (9.11.0). Same rule as
* {@link RunContext.principal}. */
readonly tenant?: string;
}
/**
* Build an EventMeta from a stage origin + run-level context.
*
* Accepts footprintjs's TraversalContext (FlowRecorder events), RecorderContext
* (WriteEvent / CommitEvent / etc.), or a bare StageOrigin. When the origin
* has no runtimeStageId (rare — manual emit during tests), the meta degrades
* gracefully to 'unknown#0'.
*/
export declare function buildEventMeta(origin: StageOrigin | TraversalContext | undefined, run: RunContext): EventMeta;
/**
* The run id an event carries when it was produced OUTSIDE any run — a
* consumer's own `agent.emit(...)`, the hosting door's redemption facts.
* One spelling, owned here beside the builder of every other run id.
*/
export declare const CONSUMER_SCOPE_RUN_ID = "consumer-scope";
/**
* Does an event belong to `run`? — the ONE answer to "which run is this event
* for?", asked by every collector that keeps a single run's events: the run's
* own recording (the `recordings` dial) and the self-explain evidence.
*
* Those collectors subscribe to the whole dispatcher and keep what arrives
* while their run is in flight. On an instance that serves more than one
* session — `standingAgent({ agent })`, where every session shares one agent —
* the dispatcher also carries facts produced for SOMEBODY ELSE at that moment:
* a screen redeeming its own chart, a host filing for its own turn, a tool
* upload from an earlier run landing late. Kept by timing alone, those facts
* entered the wrong person's recording, which that person could then redeem.
*
* Two clauses, in order:
* 1. **An event that names a run belongs to that run, and no other.** A run's
* own events carry its id through {@link buildEventMeta}; a fact emitted
* through an artifact binding carries the run the BINDING was created for
* (captured at bind time, never read from whichever run is live when the
* fact lands).
* 2. **An event of no run** ({@link CONSUMER_SCOPE_RUN_ID}) **belongs to runs
* of the session it was produced for** — the hosting door stamps what it
* produces (`RunnerBase.emitAttributed`) — **or, naming no session, to
* whatever run is in flight**, exactly as before: a consumer's own
* `agent.emit(...)` from inside a tool.
*/
export declare function eventBelongsToRun(meta: Partial<Pick<EventMeta, 'runId' | 'sessionId'>> | undefined, run: Pick<RunContext, 'runId' | 'sessionId'>): boolean;
/**
* Parse footprintjs's `/`-separated subflow path into a readonly array.
*
* The source of truth for runtimeStageId parsing lives in footprintjs at
* `foottrace::parseRuntimeStageId`. We only need the path-split
* convenience here; the `/` separator is stable across footprintjs
* versions (covered by their `parseRuntimeStageId` tests).
*/
export declare function parseSubflowPath(raw: string | undefined): readonly string[];
//# sourceMappingURL=eventMeta.d.ts.map