agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
143 lines (142 loc) • 6.33 kB
TypeScript
/**
* Sequence — sequential composition of runners (steps chained one after another).
*
* Pattern: Builder (GoF) + Adapter over footprintjs's `addSubFlowChartNext`.
* Role: core-flow/ layer — pure control flow, no LLM deps.
* Each step's output becomes the next step's input; default
* mapping is string chaining (step N's return → step N+1's
* `{ message }`). Custom mapping via `.mapBetween(fn)` between
* any two steps.
* Emits: agentfootprint.composition.enter / exit (via compositionRecorder).
*/
import { type FlowchartCheckpoint } from 'footprintjs';
import type { RunOptions } from '../core/runner.js';
import type { StructureRecorder } from 'footprintjs';
import type { GroupMetadata, GroupTranslator } from '../core/translator.js';
import type { RunnerPauseOutcome } from '../core/pause.js';
import type { Runner } from '../core/runner.js';
import { RunnerBase } from '../core/RunnerBase.js';
import type { MemoryIdentity } from '../memory/identity/types.js';
export interface SequenceOptions {
/** Human-friendly name for events + topology. Default: 'Sequence'. */
readonly name?: string;
/** Stable id used for topology + events. Default: 'sequence'. */
readonly id?: string;
/**
* Optional build-time recorders passed through to footprintjs's
* `flowChart()` factory. Each recorder observes per-node build
* events (`onStageAdded` / `onSubflowMounted` / etc.) for this
* composition's internal chart (Seed + each step mount + Finalize).
*
* Cascade: each step runner attaches its OWN recorders at its own
* construction time. footprintjs does NOT propagate StructureRecorders
* into mounted subflows — attach the same recorders to every nested
* composition for full coverage.
*
* When omitted, no build-time observation is wired up.
*/
readonly structureRecorders?: readonly StructureRecorder[];
/**
* Optional per-COMPOSITION translator (UI-agnostic). See
* `core/translator.ts`. When attached, `runner.getUIGroup()` invokes
* it with the Sequence's `GroupMetadata` (kind `'Sequence'`, id,
* name, ordered steps, no extras) and returns whatever shape the
* translator produces. When omitted, `getUIGroup()` returns
* `undefined`.
*/
readonly groupTranslator?: GroupTranslator;
}
export interface SequenceInput {
readonly message: string;
/**
* WHO this run is for — the same tuple as `AgentInput.identity`. Every
* member runs for it: its memory, its tools' `ctx.identity`. Omitted, every
* member runs under this run's own conversation (`{ conversationId: runId }`),
* never a namespace another run shares (`core/memberRun.ts`).
*/
readonly identity?: MemoryIdentity;
}
export type SequenceOutput = string;
type StepChild = Runner<{
message: string;
}, string>;
interface StepEntry {
readonly id: string;
readonly runner: StepChild;
/** Mapper applied BEFORE this step's run(). */
readonly mapFromPrev: (prev: string) => {
message: string;
};
/** Optional per-method translator override for THIS step only. */
readonly groupTranslator?: GroupTranslator;
}
/**
* Options bag accepted by `SequenceBuilder.step()` for per-method
* overrides. Backwards-compatible — when omitted the legacy two-arg
* `.step(id, runner)` signature still works.
*/
export interface SequenceStepOptions {
/** Per-method translator override. See `StepEntry.groupTranslator`. */
readonly groupTranslator?: GroupTranslator;
}
export declare class Sequence extends RunnerBase<SequenceInput, SequenceOutput> {
readonly name: string;
readonly id: string;
private readonly steps;
private readonly opts;
private currentRunContext;
constructor(opts: SequenceOptions, steps: readonly StepEntry[]);
static create(opts?: SequenceOptions): SequenceBuilder;
protected getGroupTranslator(): GroupTranslator | undefined;
/** Sequence is a flat ordered list of steps. One member per step,
* preserving definition order so the consumer can render them
* linearly (default Lens UX). Per-method overrides (L1c) take
* precedence over the step runner's own translator. */
protected buildUIGroupMetadata(): GroupMetadata;
run(input: SequenceInput | string, options?: RunOptions): Promise<SequenceOutput | RunnerPauseOutcome>;
resume(checkpoint: FlowchartCheckpoint, input?: unknown, options?: RunOptions): Promise<SequenceOutput | RunnerPauseOutcome>;
private createExecutor;
private finalizeResult;
private buildChart;
}
/**
* Fluent builder. Reads as natural English:
* Sequence.create().step('a', A).pipeVia(fn).step('b', B).build()
* → "Sequence: step A, pipe via fn, step B."
*
* `step(id, runner)` adds a sequential step. `pipeVia(fn)` customises
* the transformation of the previous step's output before it feeds the
* next step (otherwise the default string-chain mapper is used).
*/
export declare class SequenceBuilder {
private readonly opts;
private readonly steps;
/** Pending pipeVia transformer for the NEXT step (consumed on .step()). */
private pendingPipe?;
private readonly seenIds;
constructor(opts: SequenceOptions);
/**
* Add a step. Runner must accept `{ message: string }` and return `string`.
* First step receives the Sequence input; subsequent steps receive the
* previous step's output (via the default string-chain mapper, or via
* the transformer set by a preceding `.pipeVia(fn)` call).
*
* Optional third arg `opts.groupTranslator` overrides the runner's
* own constructor-level translator for THIS step only — only its
* `member.uiGroup` flips to the override's output.
*/
step(id: string, runner: StepChild, opts?: SequenceStepOptions): this;
/**
* Transform the previous step's string output before it reaches the
* next step. Consumed once by the next `.step()` call. Default
* mapping is `(prev) => ({ message: prev })`.
*
* Reads as English: `.step('a', A).pipeVia(fn).step('b', B)`
* → "step A, pipe via fn, step B"
*/
pipeVia(fn: (prev: string) => {
message: string;
}): this;
build(): Sequence;
}
export {};