v4.16.0
Fixed
-
FlowCharttype duplication eliminated. Previously, two types namedFlowChartexisted side-by-side — one inlib/engine/types.ts(used byComposableRunner.toFlowChart()return type,FlowChartExecutorconstructor,RunContext) and one inlib/builder/types.ts(used byaddSubFlowChartparameter and produced byFlowChartBuilder.build()). The two had different required fields (builder requireddescription+stageDescriptions; engine didn't), which made the documented composition patternparentBuilder.addSubFlowChart(runner.toFlowChart())fail TypeScript.FlowChartnow has a single definition inlib/builder/types.ts; all engine-side consumers import from there. Downstream libraries (e.g.,agentfootprintv2 compositions) can now typerunner.toFlowChart()intoaddSubFlowChart()without casts. -
Dead
enrichSnapshotsfield removed from theFlowCharttype. The field was read byFlowChartExecutor(fc.enrichSnapshots) but never assigned anywhere on the chart itself —flowChart().build()produces a chart with no such field.FlowChartExecutornow readsenrichSnapshotssolely from constructor options /run({ enrichSnapshots }), which was already the working path. -
Dead UI dependencies removed from
package.json. Previously,footprintjsdeclared 5 runtimedependencies(highlight.js,react-markdown,react-resizable-panels,rehype-highlight,remark-gfm) that were never imported anywhere in the library source or the documentation site (which uses Astro + Starlight with built-in Shiki highlighting). Removed — every consumer offootprintjsno longer pulls ~50 MB of unused React + markdown parsing libraries transitively.
Added
-
TopologyRecorder— sibling-to-siblingnextedges. When subflows are mounted sequentially via.addSubFlowChartNext(A).addSubFlowChartNext(B), the topology graph now emits anA → Bedge withkind: 'next'. Previously, the recorder only emitted parent→child edges, leaving the sequential transition between siblings invisible. Consumers rendering execution topology (agentfootprint-lens pipeline view, etc.) now see the realA → B → Cflow without reconstructing sibling ordering themselves. Purely additive — no existing edges removed. -
InOutRecorder— chart in/out stream with mapper payloads. NewSequenceRecorderinfootprintjs/tracethat captures every chart execution (top-levelrun()AND every subflow) as anentry/exitboundary pair. Each entry carries theinputMapperpayload (subflow) or run input (root); each exit carries theoutputMapperpayload (subflow) or chart output (root). Combined withTopologyRecorder(composition shape), this gives downstream layers (Lens, agentfootprint StepGraph) the universal "step" primitive —runtimeStageIdbinds shape to data. Path-aware viasubflowPathdecomposition (['__root__']→['__root__', 'sf-x']→ ...). Exposed asinOutRecorder()factory +InOutRecorderclass +ROOT_SUBFLOW_IDconstant. -
FlowRecorder.onRunStart/onRunEndevents. Fire ONCE per top-levelexecutor.run(), carryingevent.payload(run input on start, chart output on end). Distinct fromonSubflowEntry/onSubflowExit(which fire per subflow boundary). Available onIControlFlowNarrativeandFlowRecorderDispatcher. EnablesInOutRecorderto bracket the root run as__root__#0withisRoot: trueanddepth: 0. -
Cross-executor
resume(). A freshFlowChartExecutor(no priorrun()on the instance) can nowresume(checkpoint, input)from a serialized checkpoint — Redis / Postgres / S3 round-trip pattern. Previously the resume path implicitly required same-executor continuity and silently discardedcheckpoint.sharedState, so resume handlers came back to an empty scope. The executor now branches on a_hasRunBeforeflag: same-executor reuses the runtime; fresh-executor seeds a new runtime from the checkpoint. Same-executor behavior is preserved (execution tree + recorders + narrative still accumulate across pause/resume). Example: examples/runtime-features/pause-resume/07-cross-executor.ts. -
Subflow scope survival across pauses. A pause inside a subflow (e.g.
Sequence(Agent-that-pauses)) used to lose the subflow's isolatedSharedMemory— it was GC'd as the stack unwound, before the checkpoint was built. On resume, the inner runtime came back empty and resume handlers reading pre-pause subflow scope (e.g. an Agent'sscope.history,scope.pausedToolCallId) crashed withundefined. Fix is three-part: (1)PauseSignal.captureSubflowScope(id, state)—SubflowExecutorsnapshots inner shared memory innermost-first on the bubble-up path; (2) new requiredFlowchartCheckpoint.subflowStates: Record<subflowId, scope>field — JSON-safe, always present (empty{}for root pauses); (3)HandlerDeps.subflowStatesForResumepropagates throughFlowchartTraverser→SubflowExecutor, which re-seeds nested runtimes from the map and skips the inputMapper to preserve pre-pause state.
Fixed — Pause/Resume
- Subflow-root description propagation.
SubflowExecutorwas passing the parent mount node'sdescriptiontoFlowSubflowEvent.description, which is virtually alwaysundefined. Downstream consumers (agentfootprint, Lens) couldn't distinguish Agent subflows from LLMCall subflows by reading taxonomy markers ('Agent: ReAct loop'/'LLMCall: one-shot') on the subflow root. Fix:SubflowExecutornow readsdeps.subflows[subflowId].root.descriptionfirst, falling back tonode.description.
Changed — BREAKING
-
narrative()factory no longer decorates the recorder with.lines()/.structured()methods. These were convenience aliases for.getNarrative()/.getEntries()onCombinedNarrativeRecorder. Callers now invoke those methods directly.Migration:
// Before const rec = narrative(); const lines = rec.lines(); const entries = rec.structured(); // After const rec = narrative(); const lines = rec.getNarrative(); const entries = rec.getEntries();
-
BoundaryRecorderrenamed toInOutRecorder. The recorder shipped briefly under theBoundaryRecordername; rename clarifies what it captures (chart in/out boundaries with payloads). No consumers in the wild — this is a same-cycle rename. -
FlowchartCheckpoint.subflowStatesis now required (was optional). Always present — empty{}for root-level pauses. Tightening removes the absent-field branch in resume code; consumers readingcheckpoint.subflowStatesno longer need optional-chaining. No consumers in the wild for this field — feature shipped this same release.
Examples — Restructured
- Examples folder consolidated into a canonical tree. The legacy flat
examples/features/*andexamples/flow-recorders/*directories have been redistributed into a structured hierarchy:building-blocks/,runtime-features/{streaming,pause-resume,break,redaction,data-recorder,flow-recorder,combined-recorder,emit}/,build-time-features/{contract,self-describing,decide-select}/,post-execution/{causal-chain,quality-trace,snapshot,narrative-query}/,errors/,getting-started/,integrations/. Every file inexamples/is now type-checked on every PR vianpm run test:examples. The structure mirrorsexamples/DESIGN.md's coverage matrix; gaps in the matrix have been filled (6 new examples coveringcontract/03-mapper,self-describing/04-spec,decide-select/{02-function-rules,03-mixed-rules,04-select-parallel}, andnarrative-query/{02-entries,03-flow-narrative}). Thefootprint-playgroundsymlink already points at this tree, so playground samples track the canonical examples one-to-one.
Non-breaking overall behavior
- All composition patterns (sequential subflow chains, parallel fan-out with merge, agent ReAct loops) continue to execute identically.
- Same-executor
pause()/resume()semantics preserved — execution tree, narrative, and recorders accumulate across cycles as before. FlowCharttype change is an internal consolidation — the publicFlowChartexport from'./lib/builder/index.js'(and the top-levelfootprintjsentry) is unchanged. Only deep-import consumers from the non-public'footprintjs/lib/engine/types'path would observe a difference, and that import path is not part of the supported public surface.- Dead UI deps removal is transparent to consumers (they were never imported) — only the install footprint shrinks.