footprintjs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +658 -0
- package/dist/advanced.js +84 -0
- package/dist/esm/advanced.js +26 -0
- package/dist/esm/index.js +30 -0
- package/dist/esm/lib/builder/FlowChartBuilder.js +809 -0
- package/dist/esm/lib/builder/index.js +12 -0
- package/dist/esm/lib/builder/types.js +9 -0
- package/dist/esm/lib/engine/graph/StageNode.js +38 -0
- package/dist/esm/lib/engine/graph/index.js +2 -0
- package/dist/esm/lib/engine/handlers/ChildrenExecutor.js +116 -0
- package/dist/esm/lib/engine/handlers/ContinuationResolver.js +121 -0
- package/dist/esm/lib/engine/handlers/DeciderHandler.js +78 -0
- package/dist/esm/lib/engine/handlers/ExtractorRunner.js +117 -0
- package/dist/esm/lib/engine/handlers/NodeResolver.js +72 -0
- package/dist/esm/lib/engine/handlers/RuntimeStructureManager.js +154 -0
- package/dist/esm/lib/engine/handlers/SelectorHandler.js +79 -0
- package/dist/esm/lib/engine/handlers/StageRunner.js +86 -0
- package/dist/esm/lib/engine/handlers/SubflowExecutor.js +322 -0
- package/dist/esm/lib/engine/handlers/SubflowInputMapper.js +113 -0
- package/dist/esm/lib/engine/handlers/index.js +23 -0
- package/dist/esm/lib/engine/index.js +18 -0
- package/dist/esm/lib/engine/narrative/CombinedNarrativeBuilder.js +158 -0
- package/dist/esm/lib/engine/narrative/ControlFlowNarrativeGenerator.js +90 -0
- package/dist/esm/lib/engine/narrative/NullControlFlowNarrativeGenerator.js +24 -0
- package/dist/esm/lib/engine/narrative/index.js +4 -0
- package/dist/esm/lib/engine/narrative/types.js +11 -0
- package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +419 -0
- package/dist/esm/lib/engine/traversal/index.js +2 -0
- package/dist/esm/lib/engine/types.js +16 -0
- package/dist/esm/lib/memory/DiagnosticCollector.js +44 -0
- package/dist/esm/lib/memory/EventLog.js +44 -0
- package/dist/esm/lib/memory/SharedMemory.js +53 -0
- package/dist/esm/lib/memory/StageContext.js +221 -0
- package/dist/esm/lib/memory/TransactionBuffer.js +66 -0
- package/dist/esm/lib/memory/index.js +16 -0
- package/dist/esm/lib/memory/types.js +7 -0
- package/dist/esm/lib/memory/utils.js +155 -0
- package/dist/esm/lib/runner/ExecutionRuntime.js +64 -0
- package/dist/esm/lib/runner/FlowChartExecutor.js +108 -0
- package/dist/esm/lib/runner/index.js +3 -0
- package/dist/esm/lib/scope/ScopeFacade.js +175 -0
- package/dist/esm/lib/scope/index.js +19 -0
- package/dist/esm/lib/scope/protection/createProtectedScope.js +59 -0
- package/dist/esm/lib/scope/protection/index.js +3 -0
- package/dist/esm/lib/scope/protection/types.js +8 -0
- package/dist/esm/lib/scope/providers/baseStateCompatible.js +29 -0
- package/dist/esm/lib/scope/providers/guards.js +43 -0
- package/dist/esm/lib/scope/providers/index.js +7 -0
- package/dist/esm/lib/scope/providers/providers.js +18 -0
- package/dist/esm/lib/scope/providers/registry.js +36 -0
- package/dist/esm/lib/scope/providers/resolve.js +11 -0
- package/dist/esm/lib/scope/providers/types.js +8 -0
- package/dist/esm/lib/scope/recorders/DebugRecorder.js +81 -0
- package/dist/esm/lib/scope/recorders/MetricRecorder.js +81 -0
- package/dist/esm/lib/scope/recorders/NarrativeRecorder.js +167 -0
- package/dist/esm/lib/scope/recorders/index.js +4 -0
- package/dist/esm/lib/scope/state/installResolvers.js +15 -0
- package/dist/esm/lib/scope/state/zod/defineScopeFromZod.js +14 -0
- package/dist/esm/lib/scope/state/zod/index.js +5 -0
- package/dist/esm/lib/scope/state/zod/resolver.js +28 -0
- package/dist/esm/lib/scope/state/zod/schema/builder.js +16 -0
- package/dist/esm/lib/scope/state/zod/scopeFactory.js +156 -0
- package/dist/esm/lib/scope/state/zod/utils/validateHelper.js +97 -0
- package/dist/esm/lib/scope/types.js +9 -0
- package/dist/esm/types/advanced.d.ts +35 -0
- package/dist/esm/types/index.d.ts +22 -0
- package/dist/esm/types/lib/builder/FlowChartBuilder.d.ts +121 -0
- package/dist/esm/types/lib/builder/index.d.ts +9 -0
- package/dist/esm/types/lib/builder/types.d.ts +152 -0
- package/dist/esm/types/lib/engine/graph/StageNode.d.ts +78 -0
- package/dist/esm/types/lib/engine/graph/index.d.ts +2 -0
- package/dist/esm/types/lib/engine/handlers/ChildrenExecutor.d.ts +33 -0
- package/dist/esm/types/lib/engine/handlers/ContinuationResolver.d.ts +57 -0
- package/dist/esm/types/lib/engine/handlers/DeciderHandler.d.ts +34 -0
- package/dist/esm/types/lib/engine/handlers/ExtractorRunner.d.ts +41 -0
- package/dist/esm/types/lib/engine/handlers/NodeResolver.d.ts +26 -0
- package/dist/esm/types/lib/engine/handlers/RuntimeStructureManager.d.ts +36 -0
- package/dist/esm/types/lib/engine/handlers/SelectorHandler.d.ts +26 -0
- package/dist/esm/types/lib/engine/handlers/StageRunner.d.ts +17 -0
- package/dist/esm/types/lib/engine/handlers/SubflowExecutor.d.ts +57 -0
- package/dist/esm/types/lib/engine/handlers/SubflowInputMapper.d.ts +40 -0
- package/dist/esm/types/lib/engine/handlers/index.d.ts +15 -0
- package/dist/esm/types/lib/engine/index.d.ts +16 -0
- package/dist/esm/types/lib/engine/narrative/CombinedNarrativeBuilder.d.ts +33 -0
- package/dist/esm/types/lib/engine/narrative/ControlFlowNarrativeGenerator.d.ts +29 -0
- package/dist/esm/types/lib/engine/narrative/NullControlFlowNarrativeGenerator.d.ts +21 -0
- package/dist/esm/types/lib/engine/narrative/index.d.ts +5 -0
- package/dist/esm/types/lib/engine/narrative/types.d.ts +33 -0
- package/dist/esm/types/lib/engine/traversal/FlowchartTraverser.d.ts +87 -0
- package/dist/esm/types/lib/engine/traversal/index.d.ts +2 -0
- package/dist/esm/types/lib/engine/types.d.ts +208 -0
- package/dist/esm/types/lib/memory/DiagnosticCollector.d.ts +33 -0
- package/dist/esm/types/lib/memory/EventLog.d.ts +27 -0
- package/dist/esm/types/lib/memory/SharedMemory.d.ts +34 -0
- package/dist/esm/types/lib/memory/StageContext.d.ts +74 -0
- package/dist/esm/types/lib/memory/TransactionBuffer.d.ts +38 -0
- package/dist/esm/types/lib/memory/index.d.ts +13 -0
- package/dist/esm/types/lib/memory/types.d.ts +60 -0
- package/dist/esm/types/lib/memory/utils.d.ts +67 -0
- package/dist/esm/types/lib/runner/ExecutionRuntime.d.ts +39 -0
- package/dist/esm/types/lib/runner/FlowChartExecutor.d.ts +34 -0
- package/dist/esm/types/lib/runner/index.d.ts +3 -0
- package/dist/esm/types/lib/scope/ScopeFacade.d.ts +47 -0
- package/dist/esm/types/lib/scope/index.d.ts +23 -0
- package/dist/esm/types/lib/scope/protection/createProtectedScope.d.ts +9 -0
- package/dist/esm/types/lib/scope/protection/index.d.ts +2 -0
- package/dist/esm/types/lib/scope/protection/types.d.ts +13 -0
- package/dist/esm/types/lib/scope/providers/baseStateCompatible.d.ts +26 -0
- package/dist/esm/types/lib/scope/providers/guards.d.ts +14 -0
- package/dist/esm/types/lib/scope/providers/index.d.ts +6 -0
- package/dist/esm/types/lib/scope/providers/providers.d.ts +8 -0
- package/dist/esm/types/lib/scope/providers/registry.d.ts +11 -0
- package/dist/esm/types/lib/scope/providers/resolve.d.ts +8 -0
- package/dist/esm/types/lib/scope/providers/types.d.ts +40 -0
- package/dist/esm/types/lib/scope/recorders/DebugRecorder.d.ts +35 -0
- package/dist/esm/types/lib/scope/recorders/MetricRecorder.d.ts +36 -0
- package/dist/esm/types/lib/scope/recorders/NarrativeRecorder.d.ts +45 -0
- package/dist/esm/types/lib/scope/recorders/index.d.ts +7 -0
- package/dist/esm/types/lib/scope/state/installResolvers.d.ts +4 -0
- package/dist/esm/types/lib/scope/state/zod/defineScopeFromZod.d.ts +9 -0
- package/dist/esm/types/lib/scope/state/zod/index.d.ts +5 -0
- package/dist/esm/types/lib/scope/state/zod/resolver.d.ts +5 -0
- package/dist/esm/types/lib/scope/state/zod/schema/builder.d.ts +12 -0
- package/dist/esm/types/lib/scope/state/zod/scopeFactory.d.ts +9 -0
- package/dist/esm/types/lib/scope/state/zod/utils/validateHelper.d.ts +10 -0
- package/dist/esm/types/lib/scope/types.d.ts +53 -0
- package/dist/index.js +42 -0
- package/dist/lib/builder/FlowChartBuilder.js +817 -0
- package/dist/lib/builder/index.js +20 -0
- package/dist/lib/builder/types.js +10 -0
- package/dist/lib/engine/graph/StageNode.js +42 -0
- package/dist/lib/engine/graph/index.js +6 -0
- package/dist/lib/engine/handlers/ChildrenExecutor.js +120 -0
- package/dist/lib/engine/handlers/ContinuationResolver.js +125 -0
- package/dist/lib/engine/handlers/DeciderHandler.js +82 -0
- package/dist/lib/engine/handlers/ExtractorRunner.js +121 -0
- package/dist/lib/engine/handlers/NodeResolver.js +76 -0
- package/dist/lib/engine/handlers/RuntimeStructureManager.js +159 -0
- package/dist/lib/engine/handlers/SelectorHandler.js +83 -0
- package/dist/lib/engine/handlers/StageRunner.js +90 -0
- package/dist/lib/engine/handlers/SubflowExecutor.js +326 -0
- package/dist/lib/engine/handlers/SubflowInputMapper.js +121 -0
- package/dist/lib/engine/handlers/index.js +42 -0
- package/dist/lib/engine/index.js +40 -0
- package/dist/lib/engine/narrative/CombinedNarrativeBuilder.js +162 -0
- package/dist/lib/engine/narrative/ControlFlowNarrativeGenerator.js +94 -0
- package/dist/lib/engine/narrative/NullControlFlowNarrativeGenerator.js +28 -0
- package/dist/lib/engine/narrative/index.js +10 -0
- package/dist/lib/engine/narrative/types.js +12 -0
- package/dist/lib/engine/traversal/FlowchartTraverser.js +423 -0
- package/dist/lib/engine/traversal/index.js +6 -0
- package/dist/lib/engine/types.js +19 -0
- package/dist/lib/memory/DiagnosticCollector.js +48 -0
- package/dist/lib/memory/EventLog.js +48 -0
- package/dist/lib/memory/SharedMemory.js +60 -0
- package/dist/lib/memory/StageContext.js +225 -0
- package/dist/lib/memory/TransactionBuffer.js +73 -0
- package/dist/lib/memory/index.js +34 -0
- package/dist/lib/memory/types.js +8 -0
- package/dist/lib/memory/utils.js +170 -0
- package/dist/lib/runner/ExecutionRuntime.js +68 -0
- package/dist/lib/runner/FlowChartExecutor.js +112 -0
- package/dist/lib/runner/index.js +8 -0
- package/dist/lib/scope/ScopeFacade.js +179 -0
- package/dist/lib/scope/index.js +44 -0
- package/dist/lib/scope/protection/createProtectedScope.js +64 -0
- package/dist/lib/scope/protection/index.js +8 -0
- package/dist/lib/scope/protection/types.js +9 -0
- package/dist/lib/scope/providers/baseStateCompatible.js +33 -0
- package/dist/lib/scope/providers/guards.js +49 -0
- package/dist/lib/scope/providers/index.js +21 -0
- package/dist/lib/scope/providers/providers.js +23 -0
- package/dist/lib/scope/providers/registry.js +42 -0
- package/dist/lib/scope/providers/resolve.js +16 -0
- package/dist/lib/scope/providers/types.js +9 -0
- package/dist/lib/scope/recorders/DebugRecorder.js +85 -0
- package/dist/lib/scope/recorders/MetricRecorder.js +85 -0
- package/dist/lib/scope/recorders/NarrativeRecorder.js +171 -0
- package/dist/lib/scope/recorders/index.js +10 -0
- package/dist/lib/scope/state/installResolvers.js +19 -0
- package/dist/lib/scope/state/zod/defineScopeFromZod.js +18 -0
- package/dist/lib/scope/state/zod/index.js +13 -0
- package/dist/lib/scope/state/zod/resolver.js +31 -0
- package/dist/lib/scope/state/zod/schema/builder.js +21 -0
- package/dist/lib/scope/state/zod/scopeFactory.js +160 -0
- package/dist/lib/scope/state/zod/utils/validateHelper.js +104 -0
- package/dist/lib/scope/types.js +10 -0
- package/dist/types/advanced.d.ts +35 -0
- package/dist/types/index.d.ts +22 -0
- package/dist/types/lib/builder/FlowChartBuilder.d.ts +121 -0
- package/dist/types/lib/builder/index.d.ts +9 -0
- package/dist/types/lib/builder/types.d.ts +152 -0
- package/dist/types/lib/engine/graph/StageNode.d.ts +78 -0
- package/dist/types/lib/engine/graph/index.d.ts +2 -0
- package/dist/types/lib/engine/handlers/ChildrenExecutor.d.ts +33 -0
- package/dist/types/lib/engine/handlers/ContinuationResolver.d.ts +57 -0
- package/dist/types/lib/engine/handlers/DeciderHandler.d.ts +34 -0
- package/dist/types/lib/engine/handlers/ExtractorRunner.d.ts +41 -0
- package/dist/types/lib/engine/handlers/NodeResolver.d.ts +26 -0
- package/dist/types/lib/engine/handlers/RuntimeStructureManager.d.ts +36 -0
- package/dist/types/lib/engine/handlers/SelectorHandler.d.ts +26 -0
- package/dist/types/lib/engine/handlers/StageRunner.d.ts +17 -0
- package/dist/types/lib/engine/handlers/SubflowExecutor.d.ts +57 -0
- package/dist/types/lib/engine/handlers/SubflowInputMapper.d.ts +40 -0
- package/dist/types/lib/engine/handlers/index.d.ts +15 -0
- package/dist/types/lib/engine/index.d.ts +16 -0
- package/dist/types/lib/engine/narrative/CombinedNarrativeBuilder.d.ts +33 -0
- package/dist/types/lib/engine/narrative/ControlFlowNarrativeGenerator.d.ts +29 -0
- package/dist/types/lib/engine/narrative/NullControlFlowNarrativeGenerator.d.ts +21 -0
- package/dist/types/lib/engine/narrative/index.d.ts +5 -0
- package/dist/types/lib/engine/narrative/types.d.ts +33 -0
- package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +87 -0
- package/dist/types/lib/engine/traversal/index.d.ts +2 -0
- package/dist/types/lib/engine/types.d.ts +208 -0
- package/dist/types/lib/memory/DiagnosticCollector.d.ts +33 -0
- package/dist/types/lib/memory/EventLog.d.ts +27 -0
- package/dist/types/lib/memory/SharedMemory.d.ts +34 -0
- package/dist/types/lib/memory/StageContext.d.ts +74 -0
- package/dist/types/lib/memory/TransactionBuffer.d.ts +38 -0
- package/dist/types/lib/memory/index.d.ts +13 -0
- package/dist/types/lib/memory/types.d.ts +60 -0
- package/dist/types/lib/memory/utils.d.ts +67 -0
- package/dist/types/lib/runner/ExecutionRuntime.d.ts +39 -0
- package/dist/types/lib/runner/FlowChartExecutor.d.ts +34 -0
- package/dist/types/lib/runner/index.d.ts +3 -0
- package/dist/types/lib/scope/ScopeFacade.d.ts +47 -0
- package/dist/types/lib/scope/index.d.ts +23 -0
- package/dist/types/lib/scope/protection/createProtectedScope.d.ts +9 -0
- package/dist/types/lib/scope/protection/index.d.ts +2 -0
- package/dist/types/lib/scope/protection/types.d.ts +13 -0
- package/dist/types/lib/scope/providers/baseStateCompatible.d.ts +26 -0
- package/dist/types/lib/scope/providers/guards.d.ts +14 -0
- package/dist/types/lib/scope/providers/index.d.ts +6 -0
- package/dist/types/lib/scope/providers/providers.d.ts +8 -0
- package/dist/types/lib/scope/providers/registry.d.ts +11 -0
- package/dist/types/lib/scope/providers/resolve.d.ts +8 -0
- package/dist/types/lib/scope/providers/types.d.ts +40 -0
- package/dist/types/lib/scope/recorders/DebugRecorder.d.ts +35 -0
- package/dist/types/lib/scope/recorders/MetricRecorder.d.ts +36 -0
- package/dist/types/lib/scope/recorders/NarrativeRecorder.d.ts +45 -0
- package/dist/types/lib/scope/recorders/index.d.ts +7 -0
- package/dist/types/lib/scope/state/installResolvers.d.ts +4 -0
- package/dist/types/lib/scope/state/zod/defineScopeFromZod.d.ts +9 -0
- package/dist/types/lib/scope/state/zod/index.d.ts +5 -0
- package/dist/types/lib/scope/state/zod/resolver.d.ts +5 -0
- package/dist/types/lib/scope/state/zod/schema/builder.d.ts +12 -0
- package/dist/types/lib/scope/state/zod/scopeFactory.d.ts +9 -0
- package/dist/types/lib/scope/state/zod/utils/validateHelper.d.ts +10 -0
- package/dist/types/lib/scope/types.d.ts +53 -0
- package/package.json +148 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Sanjay Krishna Anbalagan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">FootPrint</h1>
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>Turn your whiteboard flowchart into running code — with automatic causal traces.</strong>
|
|
5
|
+
</p>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://github.com/footprintjs/footPrint/actions"><img src="https://github.com/footprintjs/footPrint/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
10
|
+
<a href="https://www.npmjs.com/package/footprint"><img src="https://img.shields.io/npm/v/footprint.svg?style=flat" alt="npm version"></a>
|
|
11
|
+
<a href="https://github.com/footprintjs/footPrint/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/footprint"><img src="https://img.shields.io/npm/dm/footprint.svg" alt="Downloads"></a>
|
|
13
|
+
<a href="https://footprintjs.github.io/footprint-playground/"><img src="https://img.shields.io/badge/Try_it-Interactive_Playground-6366f1?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJ3aGl0ZSI+PHBhdGggZD0iTTggNXYxNGwxMS03eiIvPjwvc3ZnPg==" alt="Interactive Playground"></a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<br>
|
|
17
|
+
|
|
18
|
+
FootPrint is a runtime for building **flowchart pipelines** where each node is just a function. It produces **causal traces** as a byproduct of execution — so any LLM can explain what happened and why, without reconstructing from logs.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install footprint
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Why causal traces?
|
|
27
|
+
|
|
28
|
+
A loan application pipeline rejects Bob. The user asks: **"Why was I rejected?"**
|
|
29
|
+
|
|
30
|
+
Without FootPrint, the LLM must reconstruct the reasoning from disconnected logs — expensive, slow, and unreliable. With FootPrint, the runtime produces this trace automatically from what the code actually did:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Stage 1: The process began with ReceiveApplication.
|
|
34
|
+
Step 1: Write app = {applicantName, annualIncome, monthlyDebts, creditScore, …}
|
|
35
|
+
Stage 2: Next, it moved on to PullCreditReport.
|
|
36
|
+
Step 1: Read app = {applicantName, annualIncome, monthlyDebts, creditScore, …}
|
|
37
|
+
Step 2: Write creditTier = "fair"
|
|
38
|
+
Step 3: Write creditFlags = (1 item)
|
|
39
|
+
Stage 3: Next, it moved on to CalculateDTI.
|
|
40
|
+
Step 1: Read app = {applicantName, annualIncome, monthlyDebts, creditScore, …}
|
|
41
|
+
Step 2: Write dtiRatio = 0.6
|
|
42
|
+
Step 3: Write dtiPercent = 60
|
|
43
|
+
Step 4: Write dtiStatus = "excessive"
|
|
44
|
+
Step 5: Write dtiFlags = (1 item)
|
|
45
|
+
Stage 4: Next, it moved on to VerifyEmployment.
|
|
46
|
+
Step 1: Read app = {applicantName, annualIncome, monthlyDebts, creditScore, …}
|
|
47
|
+
Step 2: Write employmentVerified = true
|
|
48
|
+
Step 3: Write employmentFlags = (1 item)
|
|
49
|
+
Stage 5: Next, it moved on to AssessRisk.
|
|
50
|
+
Step 1: Read creditTier = "fair"
|
|
51
|
+
Step 2: Read dtiStatus = "excessive"
|
|
52
|
+
Step 3: Read employmentVerified = true
|
|
53
|
+
Step 4: Write riskTier = "high"
|
|
54
|
+
Step 5: Write riskFactors = (1 item)
|
|
55
|
+
[Condition]: A decision was made, and the path taken was RejectApplication.
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The LLM backtracks through the trace: `riskTier="high"` ← `dtiStatus="excessive"` ← `dtiPercent=60` ← `app.monthlyDebts=2100`. Every variable links to its cause. Cheaper model, fewer tokens, no hallucination:
|
|
59
|
+
|
|
60
|
+
> **LLM:** "Your application was rejected because your credit score of 580 falls in the 'fair' tier, your debt-to-income ratio of 60% exceeds the 43% maximum, and your self-employment tenure of 1 year is below the 2-year minimum. These factors combined placed you in the 'high' risk tier."
|
|
61
|
+
|
|
62
|
+
That answer came from the trace — not from the LLM's imagination.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## The code that produced it
|
|
67
|
+
|
|
68
|
+
No one wrote those trace sentences. Stage functions just read and write scope — the narrative builds itself:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import {
|
|
72
|
+
FlowChartBuilder, FlowChartExecutor, ScopeFacade,
|
|
73
|
+
NarrativeRecorder, CombinedNarrativeBuilder,
|
|
74
|
+
} from 'footprint';
|
|
75
|
+
|
|
76
|
+
// ── Stage functions: just do the work, no descriptions needed ──────────
|
|
77
|
+
|
|
78
|
+
const receiveApplication = async (scope: ScopeFacade) => {
|
|
79
|
+
scope.setValue('app', app); // objects, arrays, nested — all supported
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const pullCreditReport = async (scope: ScopeFacade) => {
|
|
83
|
+
const { creditScore } = scope.getValue('app') as any;
|
|
84
|
+
const tier = creditScore >= 740 ? 'excellent' : creditScore >= 670 ? 'good'
|
|
85
|
+
: creditScore >= 580 ? 'fair' : 'poor';
|
|
86
|
+
scope.setValue('creditTier', tier);
|
|
87
|
+
scope.setValue('creditFlags', tier === 'fair' ? ['below-average credit'] : []);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const calculateDTI = async (scope: ScopeFacade) => {
|
|
91
|
+
const { annualIncome, monthlyDebts } = scope.getValue('app') as any;
|
|
92
|
+
const dtiRatio = Math.round((monthlyDebts / (annualIncome / 12)) * 100) / 100;
|
|
93
|
+
scope.setValue('dtiRatio', dtiRatio);
|
|
94
|
+
scope.setValue('dtiPercent', Math.round(dtiRatio * 100));
|
|
95
|
+
scope.setValue('dtiStatus', dtiRatio > 0.43 ? 'excessive' : 'healthy');
|
|
96
|
+
scope.setValue('dtiFlags',
|
|
97
|
+
dtiRatio > 0.43 ? [`DTI at ${Math.round(dtiRatio * 100)}% exceeds 43%`] : []);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const assessRisk = async (scope: ScopeFacade) => {
|
|
101
|
+
const creditTier = scope.getValue('creditTier') as string;
|
|
102
|
+
const dtiStatus = scope.getValue('dtiStatus') as string;
|
|
103
|
+
const verified = scope.getValue('employmentVerified') as boolean;
|
|
104
|
+
const riskTier = (!verified || dtiStatus === 'excessive' || creditTier === 'poor')
|
|
105
|
+
? 'high' : 'low';
|
|
106
|
+
scope.setValue('riskTier', riskTier);
|
|
107
|
+
scope.setValue('riskFactors', [/* collected flags */]);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const loanDecider = (scope: ScopeFacade): string => {
|
|
111
|
+
const tier = scope.getValue('riskTier') as string;
|
|
112
|
+
return tier === 'low' ? 'approved' : tier === 'high' ? 'rejected' : 'manual-review';
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// ── Build the flow ─────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
const chart = new FlowChartBuilder()
|
|
118
|
+
.setEnableNarrative()
|
|
119
|
+
.start('ReceiveApplication', receiveApplication)
|
|
120
|
+
.addFunction('PullCreditReport', pullCreditReport)
|
|
121
|
+
.addFunction('CalculateDTI', calculateDTI)
|
|
122
|
+
.addFunction('VerifyEmployment', verifyEmployment)
|
|
123
|
+
.addFunction('AssessRisk', assessRisk)
|
|
124
|
+
.addDeciderFunction('LoanDecision', loanDecider as any)
|
|
125
|
+
.addFunctionBranch('approved', 'ApproveApplication', approveFn)
|
|
126
|
+
.addFunctionBranch('rejected', 'RejectApplication', rejectFn)
|
|
127
|
+
.addFunctionBranch('manual-review', 'ManualReview', reviewFn)
|
|
128
|
+
.setDefault('manual-review')
|
|
129
|
+
.end()
|
|
130
|
+
.build();
|
|
131
|
+
|
|
132
|
+
// ── Instrument scope with NarrativeRecorder ────────────────────────────
|
|
133
|
+
|
|
134
|
+
const recorder = new NarrativeRecorder({ id: 'loan', detail: 'full' });
|
|
135
|
+
|
|
136
|
+
const scopeFactory = (ctx: any, stageName: string) => {
|
|
137
|
+
const scope = new ScopeFacade(ctx, stageName);
|
|
138
|
+
scope.attachRecorder(recorder);
|
|
139
|
+
return scope;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// ── Run and get the narrative ──────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
const executor = new FlowChartExecutor(chart, scopeFactory);
|
|
145
|
+
await executor.run();
|
|
146
|
+
|
|
147
|
+
const flowNarrative = executor.getNarrative(); // control flow sentences
|
|
148
|
+
const combined = new CombinedNarrativeBuilder();
|
|
149
|
+
const narrative = combined.build(flowNarrative, recorder); // ← the trace above
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The NarrativeRecorder observes every `getValue`/`setValue` call. The ControlFlowNarrativeGenerator captures stage transitions and decisions. CombinedNarrativeBuilder merges both into the trace. No descriptions were written by hand.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Two narratives, both auto-generated
|
|
157
|
+
|
|
158
|
+
### Build-time: tool description for LLM tool selection
|
|
159
|
+
|
|
160
|
+
When you call `.build()`, FootPrint auto-generates `chart.description` — a structural summary of what the pipeline does:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
FlowChart: ReceiveApplication
|
|
164
|
+
Steps:
|
|
165
|
+
1. ReceiveApplication
|
|
166
|
+
2. PullCreditReport
|
|
167
|
+
3. CalculateDTI
|
|
168
|
+
4. VerifyEmployment
|
|
169
|
+
5. AssessRisk
|
|
170
|
+
6. LoanDecision — Decides between: approved, rejected, manual-review
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Register this as a **tool description**. When an LLM agent has multiple tools to choose from, it can read each tool's description and pick the right one — without you writing tool descriptions by hand:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// Each flowchart's auto-generated description becomes the tool description
|
|
177
|
+
const tools = [
|
|
178
|
+
{
|
|
179
|
+
name: 'process-loan',
|
|
180
|
+
description: loanChart.description,
|
|
181
|
+
// FlowChart: ReceiveApplication
|
|
182
|
+
// Steps:
|
|
183
|
+
// 1. ReceiveApplication
|
|
184
|
+
// 2. PullCreditReport ...
|
|
185
|
+
// 6. LoanDecision — Decides between: approved, rejected, manual-review
|
|
186
|
+
handler: (input) => runWithChart(loanChart, input),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'check-fraud',
|
|
190
|
+
description: fraudChart.description,
|
|
191
|
+
// FlowChart: AnalyzeTransaction
|
|
192
|
+
// Steps:
|
|
193
|
+
// 1. AnalyzeTransaction
|
|
194
|
+
// 2. CheckVelocity
|
|
195
|
+
// 3. CheckGeolocation
|
|
196
|
+
// 4. FraudDecision — Decides between: allow, block, review
|
|
197
|
+
handler: (input) => runWithChart(fraudChart, input),
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
// The LLM sees exactly what each tool does — enough to pick the right one.
|
|
202
|
+
// No manual description writing. The structure IS the description.
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Runtime: causal trace for LLM explanation
|
|
206
|
+
|
|
207
|
+
After `.run()`, the combined narrative shows what the code *actually did* — every value read, every value written, every decision made. This is the trace shown at the top of this page. Ship it alongside the result so any follow-up LLM call can explain what happened and why.
|
|
208
|
+
|
|
209
|
+
**Build-time tells the LLM which tool to use. Runtime tells the LLM what the tool did.**
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## How it works
|
|
214
|
+
|
|
215
|
+
FootPrint has three moving parts:
|
|
216
|
+
|
|
217
|
+
1. **Scope** — Transactional state shared across stages. Writes are buffered and committed atomically. Recorders observe every read/write without modifying behavior.
|
|
218
|
+
2. **Builder** — Fluent API that compiles your flowchart into a traversable node tree.
|
|
219
|
+
3. **Engine** — DFS traversal that executes stages, manages state, and generates narrative.
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
|
223
|
+
│ FlowChartBuilder │─────>│ FlowChart │─────>│ FlowChartExecutor │
|
|
224
|
+
│ (Build-time DSL) │ │ (Compiled Tree) │ │ (Runtime Engine) │
|
|
225
|
+
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
|
|
226
|
+
│ │ │
|
|
227
|
+
│ .start() │ .build() │ .run()
|
|
228
|
+
│ .addFunction() │ .description │ .getNarrative()
|
|
229
|
+
│ .addDeciderFunction() │ .stageDescriptions │ .getSnapshot()
|
|
230
|
+
│ .addSubFlowChart() │ │
|
|
231
|
+
└────────────────────────────┴────────────────────────────┘
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Patterns
|
|
237
|
+
|
|
238
|
+
### Linear
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { flowChart } from 'footprint';
|
|
242
|
+
|
|
243
|
+
flowChart('A', fnA)
|
|
244
|
+
.addFunction('B', fnB)
|
|
245
|
+
.addFunction('C', fnC)
|
|
246
|
+
.build();
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Parallel (Fork)
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
flowChart('Fetch', fetchFn)
|
|
253
|
+
.addListOfFunction([
|
|
254
|
+
{ id: 'html', name: 'ParseHTML', fn: parseHTML },
|
|
255
|
+
{ id: 'css', name: 'ParseCSS', fn: parseCSS },
|
|
256
|
+
{ id: 'js', name: 'ParseJS', fn: parseJS },
|
|
257
|
+
])
|
|
258
|
+
.addFunction('Merge', mergeFn)
|
|
259
|
+
.build();
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Conditional (Decider)
|
|
263
|
+
|
|
264
|
+
A decider reads from scope and returns the ID of exactly one branch to execute:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
flowChart('Classify', classifyFn)
|
|
268
|
+
.addDeciderFunction('Route', (scope) => {
|
|
269
|
+
const type = scope.getValue('fulfillmentType');
|
|
270
|
+
return type === 'digital' ? 'digital' : 'physical';
|
|
271
|
+
})
|
|
272
|
+
.addFunctionBranch('digital', 'DigitalDelivery', digitalFn)
|
|
273
|
+
.addFunctionBranch('physical', 'ShipPackage', shipFn)
|
|
274
|
+
.setDefault('physical')
|
|
275
|
+
.end()
|
|
276
|
+
.build();
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Subflow Composition
|
|
280
|
+
|
|
281
|
+
Mount entire flowcharts as nodes in a larger workflow:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const faqFlow = flowChart('FAQ_Entry', faqEntryFn)
|
|
285
|
+
.addFunction('FAQ_Answer', faqAnswerFn)
|
|
286
|
+
.build();
|
|
287
|
+
|
|
288
|
+
const ragFlow = flowChart('RAG_Entry', ragEntryFn)
|
|
289
|
+
.addFunction('RAG_Retrieve', ragRetrieveFn)
|
|
290
|
+
.addFunction('RAG_Answer', ragAnswerFn)
|
|
291
|
+
.build();
|
|
292
|
+
|
|
293
|
+
const mainChart = flowChart('Router', routerFn)
|
|
294
|
+
.addSubFlowChart('faq', faqFlow, 'FAQ Handler')
|
|
295
|
+
.addSubFlowChart('rag', ragFlow, 'RAG Handler')
|
|
296
|
+
.addFunction('Aggregate', aggregateFn)
|
|
297
|
+
.build();
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Streaming (LLM)
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
const chart = flowChart('PreparePrompt', prepareFn)
|
|
304
|
+
.addStreamingFunction('AskLLM', 'llm-stream', askLLMFn)
|
|
305
|
+
.onStream((streamId, token) => process.stdout.write(token))
|
|
306
|
+
.onStreamEnd((streamId, fullText) => console.log('\nDone:', fullText))
|
|
307
|
+
.addFunction('ProcessResponse', processFn)
|
|
308
|
+
.build();
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Execution Control
|
|
312
|
+
|
|
313
|
+
Every stage receives `(scope, breakFn)`. Three levels of control:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// 1. breakFn() — Graceful stop: complete this stage, skip remaining stages
|
|
317
|
+
const validateInput = async (scope: ScopeFacade, breakFn: () => void) => {
|
|
318
|
+
const amount = scope.getValue('loanAmount') as number;
|
|
319
|
+
if (amount > 50_000) {
|
|
320
|
+
scope.setValue('rejection', 'Exceeds maximum loan amount');
|
|
321
|
+
breakFn(); // stage output is returned, no error — pipeline just stops
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// 2. throw — Hard abort: stop immediately, propagate error to caller
|
|
326
|
+
const callExternalAPI = async (scope: ScopeFacade) => {
|
|
327
|
+
const response = await fetch(scope.getValue('apiUrl') as string);
|
|
328
|
+
if (response.status === 403) {
|
|
329
|
+
throw new Error('Access denied — cannot continue'); // executor.run() rejects
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// 3. AbortSignal — External cancellation (see Cancellation & Timeout section)
|
|
334
|
+
await executor.run({ timeoutMs: 30_000 });
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
| Mechanism | Trigger | Stage completes? | Returns |
|
|
338
|
+
|-----------|---------|-----------------|---------|
|
|
339
|
+
| `breakFn()` | Inside stage | Yes | Stage output (no error) |
|
|
340
|
+
| `throw` | Inside stage | No | Error propagates |
|
|
341
|
+
| `AbortSignal` | Outside pipeline | Races async | Error propagates |
|
|
342
|
+
|
|
343
|
+
### Loops
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
flowChart('Init', initFn)
|
|
347
|
+
.addFunction('AskLLM', askFn, 'ask-llm')
|
|
348
|
+
.addFunction('ParseResponse', parseFn)
|
|
349
|
+
.addDeciderFunction('HasToolCalls', deciderFn)
|
|
350
|
+
.addFunctionBranch('yes', 'ExecuteTools', toolsFn)
|
|
351
|
+
.addFunctionBranch('no', 'Finalize', finalizeFn)
|
|
352
|
+
.end()
|
|
353
|
+
.loopTo('ask-llm') // loop back until no more tool calls
|
|
354
|
+
.build();
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Scope
|
|
360
|
+
|
|
361
|
+
Each stage receives a **scope** — a transactional interface to shared state. Writes are buffered and committed atomically after each stage. Recorders can observe every operation.
|
|
362
|
+
|
|
363
|
+
### Typed Scope (Recommended)
|
|
364
|
+
|
|
365
|
+
Extend `ScopeFacade` with domain-specific getters for type-safe reads:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { ScopeFacade } from 'footprint';
|
|
369
|
+
|
|
370
|
+
class LoanScope extends ScopeFacade {
|
|
371
|
+
get creditScore(): number {
|
|
372
|
+
return this.getValue('creditScore') as number;
|
|
373
|
+
}
|
|
374
|
+
get riskTier(): string {
|
|
375
|
+
return this.getValue('riskTier') as string;
|
|
376
|
+
}
|
|
377
|
+
get dtiStatus(): string {
|
|
378
|
+
return this.getValue('dtiStatus') as string;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const scopeFactory = (ctx: any, stageName: string) => new LoanScope(ctx, stageName);
|
|
383
|
+
|
|
384
|
+
// In stage functions:
|
|
385
|
+
const assessRisk = async (scope: LoanScope) => {
|
|
386
|
+
if (scope.creditScore < 600 || scope.dtiStatus === 'excessive') {
|
|
387
|
+
scope.setValue('riskTier', 'high'); // writes go through setValue
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
> **Why `getValue`/`setValue` instead of direct properties?** Scope protection blocks `scope.foo = bar` — those writes bypass transactional buffering and recorder hooks. Typed getters give you clean reads; `setValue` gives you tracked writes.
|
|
393
|
+
|
|
394
|
+
### Raw Scope (Low-level)
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
scope.setValue('total', 79.98); // overwrite
|
|
398
|
+
scope.updateValue('config', { retries: 3 }); // deep merge
|
|
399
|
+
const total = scope.getValue('total'); // read
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Validated Scope (Zod)
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import { z } from 'zod';
|
|
406
|
+
import { defineScopeFromZod } from 'footprint';
|
|
407
|
+
|
|
408
|
+
const schema = z.object({
|
|
409
|
+
creditScore: z.number(),
|
|
410
|
+
riskTier: z.string().optional(),
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const scopeFactory = defineScopeFromZod(schema);
|
|
414
|
+
// Proxy-based: validates writes against the schema at runtime
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Observability
|
|
420
|
+
|
|
421
|
+
### Recorders
|
|
422
|
+
|
|
423
|
+
Recorders observe scope operations without modifying them. Attach multiple for different concerns:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import {
|
|
427
|
+
ScopeFacade, DebugRecorder, MetricRecorder, NarrativeRecorder,
|
|
428
|
+
} from 'footprint';
|
|
429
|
+
|
|
430
|
+
const scopeFactory = (ctx: any, stageName: string) => {
|
|
431
|
+
const scope = new ScopeFacade(ctx, stageName);
|
|
432
|
+
scope.attachRecorder(new DebugRecorder({ verbosity: 'verbose' }));
|
|
433
|
+
scope.attachRecorder(new MetricRecorder());
|
|
434
|
+
scope.attachRecorder(new NarrativeRecorder({ id: 'trace', detail: 'full' }));
|
|
435
|
+
return scope;
|
|
436
|
+
};
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Error isolation is built in: if a recorder throws, the error is routed to `onError` hooks of other recorders, and the scope operation continues normally.
|
|
440
|
+
|
|
441
|
+
### Custom Recorders
|
|
442
|
+
|
|
443
|
+
Implement any subset of six hooks: `onRead`, `onWrite`, `onCommit`, `onError`, `onStageStart`, `onStageEnd`.
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
import { Recorder, WriteEvent } from 'footprint';
|
|
447
|
+
|
|
448
|
+
class AuditRecorder implements Recorder {
|
|
449
|
+
readonly id = 'audit';
|
|
450
|
+
private writes: Array<{ stage: string; key: string; value: unknown }> = [];
|
|
451
|
+
|
|
452
|
+
onWrite(event: WriteEvent) {
|
|
453
|
+
this.writes.push({ stage: event.stageName, key: event.key, value: event.value });
|
|
454
|
+
}
|
|
455
|
+
getWrites() { return [...this.writes]; }
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Key Features
|
|
462
|
+
|
|
463
|
+
| Feature | Description |
|
|
464
|
+
|---------|-------------|
|
|
465
|
+
| **Causal Traces** | Every read/write captured — LLMs backtrack through variables to find causes |
|
|
466
|
+
| **Auto Narrative** | Build-time descriptions for tool selection, runtime traces for explanation |
|
|
467
|
+
| **Not a DAG** | Supports loops, re-entry, and partial/resumed execution |
|
|
468
|
+
| **Parallel Fan-Out/In** | Fork pattern with automatic result aggregation (optional fail-fast) |
|
|
469
|
+
| **Cancellation** | AbortSignal and timeout support for long-running LLM calls |
|
|
470
|
+
| **Early Termination** | `breakFn()` stops the pipeline after the current stage |
|
|
471
|
+
| **Patch-Based State** | Atomic commits, safe merges, no race conditions |
|
|
472
|
+
| **Composable Subflows** | Mount entire flowcharts as nodes in larger workflows |
|
|
473
|
+
| **Streaming** | Built-in streaming stages for LLM token emission |
|
|
474
|
+
| **Pluggable Recorders** | DebugRecorder, MetricRecorder, NarrativeRecorder — or bring your own |
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Cancellation & Timeout
|
|
479
|
+
|
|
480
|
+
For LLM pipelines where API calls can hang, `FlowChartExecutor.run()` supports cooperative cancellation:
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
// Timeout: auto-abort after 30 seconds
|
|
484
|
+
const result = await executor.run({ timeoutMs: 30_000 });
|
|
485
|
+
|
|
486
|
+
// AbortSignal: cancel from outside
|
|
487
|
+
const controller = new AbortController();
|
|
488
|
+
setTimeout(() => controller.abort(), 10_000); // cancel after 10s
|
|
489
|
+
const result = await executor.run({ signal: controller.signal });
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
The signal is checked before each stage starts and raced against async stage functions. Aborted executions throw with the signal's reason.
|
|
493
|
+
|
|
494
|
+
## Fail-Fast Forks
|
|
495
|
+
|
|
496
|
+
By default, parallel children run to completion even if some fail (errors captured as `{ isError: true }`). For cases where you want immediate failure:
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
flowChart('Fetch', fetchFn)
|
|
500
|
+
.addListOfFunction([
|
|
501
|
+
{ id: 'api1', name: 'CallAPI1', fn: api1Fn },
|
|
502
|
+
{ id: 'api2', name: 'CallAPI2', fn: api2Fn },
|
|
503
|
+
], { failFast: true }) // first child error rejects the whole fork
|
|
504
|
+
.build();
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## Design: Error Handling
|
|
508
|
+
|
|
509
|
+
FootPrint's error handling is designed around one principle: **the trace must capture everything that happened, including failures**.
|
|
510
|
+
|
|
511
|
+
### Who is responsible for what
|
|
512
|
+
|
|
513
|
+
| Layer | Responsibility |
|
|
514
|
+
|-------|---------------|
|
|
515
|
+
| **Stage function** | Business logic. Throws errors when invariants break. |
|
|
516
|
+
| **Engine** | Infrastructure. Catches errors, commits the trace, records error metadata, then re-throws. |
|
|
517
|
+
| **Consumer** | Wraps `executor.run()` in try/catch. Inspects `getSnapshot()` after failure for debugging. |
|
|
518
|
+
|
|
519
|
+
### Commit-on-error: why it matters
|
|
520
|
+
|
|
521
|
+
When a stage throws, the engine calls `context.commit()` *before* re-throwing. This means:
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
const executor = new FlowChartExecutor(chart, scopeFactory);
|
|
525
|
+
try {
|
|
526
|
+
await executor.run();
|
|
527
|
+
} catch (error) {
|
|
528
|
+
// The snapshot captures everything up to the failure point
|
|
529
|
+
const snapshot = executor.getSnapshot();
|
|
530
|
+
// commitLog has entries for every stage that ran (including the one that failed)
|
|
531
|
+
// executionTree shows scope writes, error metadata, and flow decisions
|
|
532
|
+
// An LLM can use this to explain WHY the error happened
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Without commit-on-error, a failed stage's partial writes would be lost. The trace would end at the last successful stage, hiding the context of the failure. Commit-on-error gives consumers complete visibility:
|
|
537
|
+
|
|
538
|
+
- **Scope writes** made before the throw are preserved
|
|
539
|
+
- **Error metadata** (`stageExecutionError`) is recorded in the execution tree
|
|
540
|
+
- **Narrative** includes the error event (`"An error occurred at validate: ..."`)
|
|
541
|
+
- **Commit log** has an entry for the failed stage's state
|
|
542
|
+
|
|
543
|
+
### Error narrative in practice
|
|
544
|
+
|
|
545
|
+
A validation pipeline fails. The trace tells the story:
|
|
546
|
+
|
|
547
|
+
```
|
|
548
|
+
Stage 1: The process began with FetchData.
|
|
549
|
+
Step 1: Write rawPayload = {name: "Bob", age: -5}
|
|
550
|
+
Stage 2: Next, it moved on to Validate.
|
|
551
|
+
Step 1: Read rawPayload = {name: "Bob", age: -5}
|
|
552
|
+
An error occurred at Validate: Validation failed: age must be positive.
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
An LLM reading this trace can immediately explain: *"The validation failed because the age field was -5, which was provided in the raw payload from FetchData. Age must be a positive number."* No log reconstruction needed.
|
|
556
|
+
|
|
557
|
+
### What consumers can do
|
|
558
|
+
|
|
559
|
+
- **Retry with modifications**: Inspect the snapshot, fix inputs, re-run
|
|
560
|
+
- **Partial results**: Fork children that succeed still return results (default mode)
|
|
561
|
+
- **Fail-fast**: Opt into `failFast: true` when any child error should abort the whole fork
|
|
562
|
+
- **Timeout/cancel**: Use `timeoutMs` or `AbortSignal` for external cancellation
|
|
563
|
+
- **Post-mortem**: Feed the narrative + snapshot to an LLM for root-cause analysis
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## API Reference
|
|
568
|
+
|
|
569
|
+
### Builder
|
|
570
|
+
|
|
571
|
+
| Method | Description |
|
|
572
|
+
|--------|-------------|
|
|
573
|
+
| `start(name, fn?)` | Define root stage |
|
|
574
|
+
| `addFunction(name, fn?)` | Add linear next stage |
|
|
575
|
+
| `addListOfFunction(specs, opts?)` | Add parallel children (fork). Options: `{ failFast? }` |
|
|
576
|
+
| `addDeciderFunction(name, fn)` | Single-choice branching (returns one branch ID) |
|
|
577
|
+
| `addSelectorFunction(name, fn)` | Multi-choice branching (returns multiple branch IDs) |
|
|
578
|
+
| `addSubFlowChart(id, flow)` | Mount subflow as parallel child |
|
|
579
|
+
| `addSubFlowChartNext(id, flow)` | Mount subflow as linear next |
|
|
580
|
+
| `addStreamingFunction(name, streamId?, fn?)` | Add streaming stage |
|
|
581
|
+
| `addTraversalExtractor(fn)` | Register per-stage data extractor |
|
|
582
|
+
| `setEnableNarrative()` | Enable runtime narrative generation |
|
|
583
|
+
| `loopTo(stageId)` | Loop back to earlier stage |
|
|
584
|
+
| `build()` | Compile to FlowChart |
|
|
585
|
+
| `execute(scopeFactory)` | Build + run (convenience) |
|
|
586
|
+
| `toSpec()` | Export pure JSON (no functions) |
|
|
587
|
+
| `toMermaid()` | Generate Mermaid diagram |
|
|
588
|
+
|
|
589
|
+
### Executor
|
|
590
|
+
|
|
591
|
+
| Method | Description |
|
|
592
|
+
|--------|-------------|
|
|
593
|
+
| `run(options?)` | Execute the flowchart. Options: `{ signal?, timeoutMs? }` |
|
|
594
|
+
| `getNarrative()` | Control-flow narrative sentences |
|
|
595
|
+
| `getSnapshot()` | Full execution tree + state |
|
|
596
|
+
| `getExtractedResults()` | Extractor results map |
|
|
597
|
+
| `getEnrichedResults()` | Enriched snapshots (scope state, debug info, output) |
|
|
598
|
+
| `getSubflowResults()` | Nested subflow execution data |
|
|
599
|
+
| `getRuntimeStructure()` | Serialized pipeline for visualization |
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
## How FootPrint Compares
|
|
604
|
+
|
|
605
|
+
| Aspect | async/await | FootPrint | Temporal / Step Functions |
|
|
606
|
+
|--------|-------------|-----------|--------------------------|
|
|
607
|
+
| **Control Flow** | Implicit in code | Explicit flowchart | External orchestrator |
|
|
608
|
+
| **State** | Manual/global | Scoped & transactional | Durable storage |
|
|
609
|
+
| **Debugging** | Stack traces | Time-travel replay | Event history |
|
|
610
|
+
| **LLM Narrative** | None | Automatic from operations | None |
|
|
611
|
+
| **Tool Descriptions** | Manual | Auto-generated from structure | Manual |
|
|
612
|
+
| **Complexity** | Low | Medium | High |
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## Performance
|
|
617
|
+
|
|
618
|
+
Measured on Node v22, Apple Silicon. Run `npm run bench` to reproduce.
|
|
619
|
+
|
|
620
|
+
| Benchmark | Time | Detail |
|
|
621
|
+
|-----------|------|--------|
|
|
622
|
+
| **Write 1K keys** | 811µs | ~1.2M ops/s |
|
|
623
|
+
| **Write 10K keys** | 5.4ms | ~1.8M ops/s |
|
|
624
|
+
| **Read 100K keys** | 8.7ms | ~11.5M ops/s |
|
|
625
|
+
| **10 stages (linear)** | 106µs | 0.011ms/stage |
|
|
626
|
+
| **200 stages (linear)** | 4.7ms | 0.023ms/stage |
|
|
627
|
+
| **500 stages (linear)** | 20ms | 0.040ms/stage |
|
|
628
|
+
| **100 concurrent pipelines** | 2.3ms | 3-stage each |
|
|
629
|
+
| **1,000 concurrent pipelines** | 24ms | 3-stage each |
|
|
630
|
+
| **structuredClone 1KB** | 2µs | per call |
|
|
631
|
+
| **structuredClone 100KB** | 76µs | per call |
|
|
632
|
+
| **structuredClone 1MB** | 2.5ms | per call |
|
|
633
|
+
| **Time-travel 100 commits** | 75µs | 0.001ms/commit |
|
|
634
|
+
| **Time-travel 500 commits** | 385µs | 0.001ms/commit |
|
|
635
|
+
| **Commit with 100 writes** | 375µs | single stage |
|
|
636
|
+
|
|
637
|
+
**Bottom line:** A 200-stage pipeline completes in under 5ms. The primary cost at scale is `structuredClone` — keep state objects under 100KB per stage for sub-millisecond commit overhead.
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## Architecture
|
|
642
|
+
|
|
643
|
+
FootPrint is five independent libraries, each usable standalone:
|
|
644
|
+
|
|
645
|
+
```
|
|
646
|
+
src/lib/
|
|
647
|
+
├── memory/ Transactional state (SharedMemory, StageContext, EventLog, TransactionBuffer)
|
|
648
|
+
├── builder/ Fluent flowchart DSL (FlowChartBuilder, DeciderList, SelectorFnList)
|
|
649
|
+
├── scope/ Scope facades, recorders, protection, Zod integration
|
|
650
|
+
├── engine/ DFS traversal, handlers, narrative generators
|
|
651
|
+
└── runner/ Execution convenience (FlowChartExecutor, ExecutionRuntime)
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## License
|
|
657
|
+
|
|
658
|
+
[MIT](./LICENSE) © [Sanjay Krishna Anbalagan](https://github.com/sanjay1909)
|