@falai/agent 1.2.8 → 2.0.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/README.md +40 -886
- package/dist/adapters/MemoryAdapter.js +2 -2
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/MongoAdapter.js +2 -2
- package/dist/adapters/MongoAdapter.js.map +1 -1
- package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
- package/dist/adapters/OpenSearchAdapter.js +9 -7
- package/dist/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts +14 -0
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +25 -9
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/PrismaAdapter.js +5 -5
- package/dist/adapters/PrismaAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.js +2 -2
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts +17 -0
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js +30 -11
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/MemoryAdapter.js +2 -2
- package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
- package/dist/cjs/adapters/MongoAdapter.js +2 -2
- package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
- package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/OpenSearchAdapter.js +9 -7
- package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +14 -0
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +25 -9
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/cjs/adapters/PrismaAdapter.js +5 -5
- package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.js +2 -2
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts +17 -0
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.js +30 -11
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/constants/index.d.ts +0 -9
- package/dist/cjs/constants/index.d.ts.map +1 -1
- package/dist/cjs/constants/index.js +2 -11
- package/dist/cjs/constants/index.js.map +1 -1
- package/dist/cjs/core/Agent.d.ts +119 -153
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +471 -324
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.d.ts +107 -0
- package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -0
- package/dist/cjs/core/AutoChainExecutor.js +297 -0
- package/dist/cjs/core/AutoChainExecutor.js.map +1 -0
- package/dist/cjs/core/BranchEvaluator.d.ts +54 -0
- package/dist/cjs/core/BranchEvaluator.d.ts.map +1 -0
- package/dist/cjs/core/BranchEvaluator.js +130 -0
- package/dist/cjs/core/BranchEvaluator.js.map +1 -0
- package/dist/cjs/core/DirectiveBus.d.ts +88 -0
- package/dist/cjs/core/DirectiveBus.d.ts.map +1 -0
- package/dist/cjs/core/DirectiveBus.js +196 -0
- package/dist/cjs/core/DirectiveBus.js.map +1 -0
- package/dist/cjs/core/DirectiveChainTracker.d.ts +49 -0
- package/dist/cjs/core/DirectiveChainTracker.d.ts.map +1 -0
- package/dist/cjs/core/DirectiveChainTracker.js +121 -0
- package/dist/cjs/core/DirectiveChainTracker.js.map +1 -0
- package/dist/cjs/core/Flow.d.ts +186 -0
- package/dist/cjs/core/Flow.d.ts.map +1 -0
- package/dist/cjs/core/Flow.js +550 -0
- package/dist/cjs/core/Flow.js.map +1 -0
- package/dist/cjs/core/FlowRouter.d.ts +182 -0
- package/dist/cjs/core/FlowRouter.d.ts.map +1 -0
- package/dist/cjs/core/{RoutingEngine.js → FlowRouter.js} +323 -306
- package/dist/cjs/core/FlowRouter.js.map +1 -0
- package/dist/cjs/core/PersistenceManager.d.ts +2 -2
- package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
- package/dist/cjs/core/PersistenceManager.js +7 -7
- package/dist/cjs/core/PersistenceManager.js.map +1 -1
- package/dist/cjs/core/PromptComposer.d.ts +21 -8
- package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
- package/dist/cjs/core/PromptComposer.js +182 -105
- package/dist/cjs/core/PromptComposer.js.map +1 -1
- package/dist/cjs/core/PromptSectionCache.d.ts +1 -1
- package/dist/cjs/core/PromptSectionCache.js +1 -1
- package/dist/cjs/core/ResponseEngine.d.ts +18 -8
- package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
- package/dist/cjs/core/ResponseEngine.js +38 -36
- package/dist/cjs/core/ResponseEngine.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts +73 -56
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +1191 -1014
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +124 -26
- package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/core/ResponsePipeline.js +509 -136
- package/dist/cjs/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/core/SignalEvaluator.d.ts +86 -0
- package/dist/cjs/core/SignalEvaluator.d.ts.map +1 -0
- package/dist/cjs/core/SignalEvaluator.js +333 -0
- package/dist/cjs/core/SignalEvaluator.js.map +1 -0
- package/dist/cjs/core/SignalProcessor.d.ts +152 -0
- package/dist/cjs/core/SignalProcessor.d.ts.map +1 -0
- package/dist/cjs/core/SignalProcessor.js +562 -0
- package/dist/cjs/core/SignalProcessor.js.map +1 -0
- package/dist/cjs/core/Step.d.ts +43 -32
- package/dist/cjs/core/Step.d.ts.map +1 -1
- package/dist/cjs/core/Step.js +221 -126
- package/dist/cjs/core/Step.js.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts +2 -2
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
- package/dist/cjs/core/ToolManager.d.ts +44 -13
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +174 -91
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/core/createAgent.d.ts +35 -0
- package/dist/cjs/core/createAgent.d.ts.map +1 -0
- package/dist/cjs/core/createAgent.js +39 -0
- package/dist/cjs/core/createAgent.js.map +1 -0
- package/dist/cjs/core/flow-namespace.d.ts +49 -0
- package/dist/cjs/core/flow-namespace.d.ts.map +1 -0
- package/dist/cjs/core/flow-namespace.js +171 -0
- package/dist/cjs/core/flow-namespace.js.map +1 -0
- package/dist/cjs/index.d.ts +11 -14
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +18 -22
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +183 -54
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js +0 -6
- package/dist/cjs/types/agent.js.map +1 -1
- package/dist/cjs/types/ai.d.ts +3 -3
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/errors.d.ts +15 -0
- package/dist/cjs/types/errors.d.ts.map +1 -0
- package/dist/cjs/types/errors.js +22 -0
- package/dist/cjs/types/errors.js.map +1 -0
- package/dist/cjs/types/flow.d.ts +513 -0
- package/dist/cjs/types/flow.d.ts.map +1 -0
- package/dist/cjs/types/{route.js → flow.js} +2 -2
- package/dist/cjs/types/flow.js.map +1 -0
- package/dist/cjs/types/index.d.ts +7 -6
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +6 -2
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/persistence.d.ts +11 -7
- package/dist/cjs/types/persistence.d.ts.map +1 -1
- package/dist/cjs/types/routing.d.ts +1 -1
- package/dist/cjs/types/routing.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +24 -23
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/types/signals.d.ts +248 -0
- package/dist/cjs/types/signals.d.ts.map +1 -0
- package/dist/cjs/types/signals.js +11 -0
- package/dist/cjs/types/signals.js.map +1 -0
- package/dist/cjs/types/template.d.ts +2 -8
- package/dist/cjs/types/template.d.ts.map +1 -1
- package/dist/cjs/types/tool.d.ts +36 -29
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/types/tool.js +1 -1
- package/dist/cjs/types/tool.js.map +1 -1
- package/dist/cjs/utils/condition.d.ts +7 -1
- package/dist/cjs/utils/condition.d.ts.map +1 -1
- package/dist/cjs/utils/condition.js.map +1 -1
- package/dist/cjs/utils/id.d.ts +13 -5
- package/dist/cjs/utils/id.d.ts.map +1 -1
- package/dist/cjs/utils/id.js +24 -10
- package/dist/cjs/utils/id.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +2 -2
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +7 -3
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/session.d.ts +44 -5
- package/dist/cjs/utils/session.d.ts.map +1 -1
- package/dist/cjs/utils/session.js +197 -38
- package/dist/cjs/utils/session.js.map +1 -1
- package/dist/constants/index.d.ts +0 -9
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +3 -9
- package/dist/constants/index.js.map +1 -1
- package/dist/core/Agent.d.ts +119 -153
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +472 -325
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/AutoChainExecutor.d.ts +107 -0
- package/dist/core/AutoChainExecutor.d.ts.map +1 -0
- package/dist/core/AutoChainExecutor.js +293 -0
- package/dist/core/AutoChainExecutor.js.map +1 -0
- package/dist/core/BranchEvaluator.d.ts +54 -0
- package/dist/core/BranchEvaluator.d.ts.map +1 -0
- package/dist/core/BranchEvaluator.js +126 -0
- package/dist/core/BranchEvaluator.js.map +1 -0
- package/dist/core/DirectiveBus.d.ts +88 -0
- package/dist/core/DirectiveBus.d.ts.map +1 -0
- package/dist/core/DirectiveBus.js +192 -0
- package/dist/core/DirectiveBus.js.map +1 -0
- package/dist/core/DirectiveChainTracker.d.ts +49 -0
- package/dist/core/DirectiveChainTracker.d.ts.map +1 -0
- package/dist/core/DirectiveChainTracker.js +117 -0
- package/dist/core/DirectiveChainTracker.js.map +1 -0
- package/dist/core/Flow.d.ts +186 -0
- package/dist/core/Flow.d.ts.map +1 -0
- package/dist/core/Flow.js +546 -0
- package/dist/core/Flow.js.map +1 -0
- package/dist/core/FlowRouter.d.ts +182 -0
- package/dist/core/FlowRouter.d.ts.map +1 -0
- package/dist/core/{RoutingEngine.js → FlowRouter.js} +322 -305
- package/dist/core/FlowRouter.js.map +1 -0
- package/dist/core/PersistenceManager.d.ts +2 -2
- package/dist/core/PersistenceManager.d.ts.map +1 -1
- package/dist/core/PersistenceManager.js +7 -7
- package/dist/core/PersistenceManager.js.map +1 -1
- package/dist/core/PromptComposer.d.ts +21 -8
- package/dist/core/PromptComposer.d.ts.map +1 -1
- package/dist/core/PromptComposer.js +183 -106
- package/dist/core/PromptComposer.js.map +1 -1
- package/dist/core/PromptSectionCache.d.ts +1 -1
- package/dist/core/PromptSectionCache.js +1 -1
- package/dist/core/ResponseEngine.d.ts +18 -8
- package/dist/core/ResponseEngine.d.ts.map +1 -1
- package/dist/core/ResponseEngine.js +38 -36
- package/dist/core/ResponseEngine.js.map +1 -1
- package/dist/core/ResponseModal.d.ts +73 -56
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +1193 -1016
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/ResponsePipeline.d.ts +124 -26
- package/dist/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/core/ResponsePipeline.js +509 -137
- package/dist/core/ResponsePipeline.js.map +1 -1
- package/dist/core/SignalEvaluator.d.ts +86 -0
- package/dist/core/SignalEvaluator.d.ts.map +1 -0
- package/dist/core/SignalEvaluator.js +326 -0
- package/dist/core/SignalEvaluator.js.map +1 -0
- package/dist/core/SignalProcessor.d.ts +152 -0
- package/dist/core/SignalProcessor.d.ts.map +1 -0
- package/dist/core/SignalProcessor.js +555 -0
- package/dist/core/SignalProcessor.js.map +1 -0
- package/dist/core/Step.d.ts +43 -32
- package/dist/core/Step.d.ts.map +1 -1
- package/dist/core/Step.js +220 -126
- package/dist/core/Step.js.map +1 -1
- package/dist/core/StreamingToolExecutor.d.ts +2 -2
- package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/core/StreamingToolExecutor.js.map +1 -1
- package/dist/core/ToolManager.d.ts +44 -13
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +174 -91
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/core/createAgent.d.ts +35 -0
- package/dist/core/createAgent.d.ts.map +1 -0
- package/dist/core/createAgent.js +36 -0
- package/dist/core/createAgent.js.map +1 -0
- package/dist/core/flow-namespace.d.ts +49 -0
- package/dist/core/flow-namespace.d.ts.map +1 -0
- package/dist/core/flow-namespace.js +168 -0
- package/dist/core/flow-namespace.js.map +1 -0
- package/dist/index.d.ts +11 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -12
- package/dist/index.js.map +1 -1
- package/dist/types/agent.d.ts +183 -54
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js +0 -6
- package/dist/types/agent.js.map +1 -1
- package/dist/types/ai.d.ts +3 -3
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +18 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/flow.d.ts +513 -0
- package/dist/types/flow.d.ts.map +1 -0
- package/dist/types/flow.js +5 -0
- package/dist/types/flow.js.map +1 -0
- package/dist/types/index.d.ts +7 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/persistence.d.ts +11 -7
- package/dist/types/persistence.d.ts.map +1 -1
- package/dist/types/routing.d.ts +1 -1
- package/dist/types/routing.d.ts.map +1 -1
- package/dist/types/session.d.ts +24 -23
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/signals.d.ts +248 -0
- package/dist/types/signals.d.ts.map +1 -0
- package/dist/types/signals.js +10 -0
- package/dist/types/signals.js.map +1 -0
- package/dist/types/template.d.ts +2 -8
- package/dist/types/template.d.ts.map +1 -1
- package/dist/types/tool.d.ts +36 -29
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/tool.js +1 -1
- package/dist/types/tool.js.map +1 -1
- package/dist/utils/condition.d.ts +7 -1
- package/dist/utils/condition.d.ts.map +1 -1
- package/dist/utils/condition.js.map +1 -1
- package/dist/utils/id.d.ts +13 -5
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +22 -9
- package/dist/utils/id.js.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/session.d.ts +44 -5
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +193 -37
- package/dist/utils/session.js.map +1 -1
- package/docs/README.md +15 -202
- package/docs/concepts/architecture.md +281 -0
- package/docs/concepts/directives.md +400 -0
- package/docs/concepts/pipeline.md +399 -0
- package/docs/guides/branching.md +263 -0
- package/docs/guides/compaction.md +163 -0
- package/docs/guides/conditions.md +167 -0
- package/docs/guides/error-handling.md +176 -0
- package/docs/guides/flow-control.md +409 -0
- package/docs/guides/instructions.md +210 -0
- package/docs/guides/persistence.md +182 -0
- package/docs/guides/streaming.md +137 -0
- package/docs/migration/README.md +15 -0
- package/docs/migration/route-to-flow.md +560 -0
- package/docs/migration/v1-to-v2.md +909 -0
- package/docs/reference/adapters.md +481 -0
- package/docs/reference/branches.md +241 -0
- package/docs/reference/create-agent.md +186 -0
- package/docs/reference/directive.md +243 -0
- package/docs/reference/errors.md +122 -0
- package/docs/reference/flow.md +238 -0
- package/docs/reference/instruction.md +177 -0
- package/docs/reference/pre-directive.md +131 -0
- package/docs/reference/providers.md +227 -0
- package/docs/reference/signals.md +356 -0
- package/docs/reference/step.md +339 -0
- package/docs/reference/tool.md +269 -0
- package/docs/start/01-install.md +81 -0
- package/docs/start/02-first-agent.md +196 -0
- package/docs/start/03-collect-data.md +222 -0
- package/docs/start/04-add-tools.md +276 -0
- package/docs/start/05-go-to-production.md +216 -0
- package/examples/01-quickstart.ts +20 -0
- package/examples/02-data-extraction.ts +90 -0
- package/examples/03-tools.ts +136 -0
- package/examples/04-instructions.ts +100 -0
- package/examples/05-branching.ts +140 -0
- package/examples/06-flow-control.ts +103 -0
- package/examples/07-streaming.ts +69 -0
- package/examples/08-persistence.ts +98 -0
- package/examples/09-signals.ts +144 -0
- package/examples/tsconfig.json +30 -0
- package/package.json +2 -1
- package/src/adapters/MemoryAdapter.ts +3 -3
- package/src/adapters/MongoAdapter.ts +3 -3
- package/src/adapters/OpenSearchAdapter.ts +10 -8
- package/src/adapters/PostgreSQLAdapter.ts +26 -10
- package/src/adapters/PrismaAdapter.ts +6 -6
- package/src/adapters/RedisAdapter.ts +3 -3
- package/src/adapters/SQLiteAdapter.ts +31 -12
- package/src/constants/index.ts +2 -10
- package/src/core/Agent.ts +585 -374
- package/src/core/AutoChainExecutor.ts +440 -0
- package/src/core/BranchEvaluator.ts +167 -0
- package/src/core/DirectiveBus.ts +248 -0
- package/src/core/DirectiveChainTracker.ts +144 -0
- package/src/core/Flow.ts +666 -0
- package/src/core/{RoutingEngine.ts → FlowRouter.ts} +385 -365
- package/src/core/PersistenceManager.ts +8 -8
- package/src/core/PromptComposer.ts +209 -140
- package/src/core/PromptSectionCache.ts +1 -1
- package/src/core/ResponseEngine.ts +61 -46
- package/src/core/ResponseModal.ts +1453 -1240
- package/src/core/ResponsePipeline.ts +655 -175
- package/src/core/SignalEvaluator.ts +420 -0
- package/src/core/SignalProcessor.ts +723 -0
- package/src/core/Step.ts +279 -176
- package/src/core/StreamingToolExecutor.ts +4 -4
- package/src/core/ToolManager.ts +200 -97
- package/src/core/createAgent.ts +40 -0
- package/src/core/flow-namespace.ts +219 -0
- package/src/index.ts +42 -36
- package/src/types/agent.ts +182 -53
- package/src/types/ai.ts +3 -3
- package/src/types/errors.ts +18 -0
- package/src/types/flow.ts +590 -0
- package/src/types/index.ts +43 -16
- package/src/types/persistence.ts +12 -8
- package/src/types/routing.ts +1 -1
- package/src/types/session.ts +26 -23
- package/src/types/signals.ts +321 -0
- package/src/types/template.ts +3 -11
- package/src/types/tool.ts +50 -42
- package/src/utils/condition.ts +13 -4
- package/src/utils/id.ts +27 -9
- package/src/utils/index.ts +6 -2
- package/src/utils/session.ts +238 -42
- package/dist/cjs/core/BatchExecutor.d.ts +0 -359
- package/dist/cjs/core/BatchExecutor.d.ts.map +0 -1
- package/dist/cjs/core/BatchExecutor.js +0 -861
- package/dist/cjs/core/BatchExecutor.js.map +0 -1
- package/dist/cjs/core/BatchPromptBuilder.d.ts +0 -89
- package/dist/cjs/core/BatchPromptBuilder.d.ts.map +0 -1
- package/dist/cjs/core/BatchPromptBuilder.js +0 -223
- package/dist/cjs/core/BatchPromptBuilder.js.map +0 -1
- package/dist/cjs/core/Route.d.ts +0 -180
- package/dist/cjs/core/Route.d.ts.map +0 -1
- package/dist/cjs/core/Route.js +0 -542
- package/dist/cjs/core/Route.js.map +0 -1
- package/dist/cjs/core/RoutingEngine.d.ts +0 -185
- package/dist/cjs/core/RoutingEngine.d.ts.map +0 -1
- package/dist/cjs/core/RoutingEngine.js.map +0 -1
- package/dist/cjs/types/route.d.ts +0 -336
- package/dist/cjs/types/route.d.ts.map +0 -1
- package/dist/cjs/types/route.js.map +0 -1
- package/dist/core/BatchExecutor.d.ts +0 -359
- package/dist/core/BatchExecutor.d.ts.map +0 -1
- package/dist/core/BatchExecutor.js +0 -856
- package/dist/core/BatchExecutor.js.map +0 -1
- package/dist/core/BatchPromptBuilder.d.ts +0 -89
- package/dist/core/BatchPromptBuilder.d.ts.map +0 -1
- package/dist/core/BatchPromptBuilder.js +0 -219
- package/dist/core/BatchPromptBuilder.js.map +0 -1
- package/dist/core/Route.d.ts +0 -180
- package/dist/core/Route.d.ts.map +0 -1
- package/dist/core/Route.js +0 -538
- package/dist/core/Route.js.map +0 -1
- package/dist/core/RoutingEngine.d.ts +0 -185
- package/dist/core/RoutingEngine.d.ts.map +0 -1
- package/dist/core/RoutingEngine.js.map +0 -1
- package/dist/types/route.d.ts +0 -336
- package/dist/types/route.d.ts.map +0 -1
- package/dist/types/route.js +0 -5
- package/dist/types/route.js.map +0 -1
- package/docs/CONTRIBUTING.md +0 -521
- package/docs/api/README.md +0 -3299
- package/docs/api/overview.md +0 -1410
- package/docs/architecture/data-extraction-flow.md +0 -360
- package/docs/architecture/multi-step-execution.md +0 -277
- package/docs/core/agent/README.md +0 -938
- package/docs/core/agent/context-management.md +0 -796
- package/docs/core/agent/rules-and-prohibitions.md +0 -113
- package/docs/core/agent/session-management.md +0 -693
- package/docs/core/ai-integration/prompt-composition.md +0 -355
- package/docs/core/ai-integration/providers.md +0 -515
- package/docs/core/ai-integration/response-processing.md +0 -433
- package/docs/core/conversation-flows/data-collection.md +0 -772
- package/docs/core/conversation-flows/route-dsl.md +0 -509
- package/docs/core/conversation-flows/routes.md +0 -249
- package/docs/core/conversation-flows/step-transitions.md +0 -731
- package/docs/core/conversation-flows/steps.md +0 -268
- package/docs/core/error-handling.md +0 -830
- package/docs/core/persistence/adapters.md +0 -255
- package/docs/core/persistence/session-storage.md +0 -656
- package/docs/core/routing/intelligent-routing.md +0 -470
- package/docs/core/tools/enhanced-tool.md +0 -186
- package/docs/core/tools/streaming-execution.md +0 -161
- package/docs/core/tools/tool-definition.md +0 -970
- package/docs/core/tools/tool-scoping.md +0 -819
- package/docs/guides/advanced-patterns/publishing.md +0 -186
- package/docs/guides/context-compaction.md +0 -96
- package/docs/guides/error-handling-patterns.md +0 -578
- package/docs/guides/getting-started/README.md +0 -795
- package/docs/guides/migration/README.md +0 -101
- package/docs/guides/migration/flexible-routing-conditions.md +0 -375
- package/docs/guides/migration/multi-step-execution.md +0 -393
- package/docs/guides/migration/response-modal-refactor.md +0 -518
- package/docs/guides/prompt-optimization.md +0 -164
- package/examples/advanced-patterns/context-compaction.ts +0 -223
- package/examples/advanced-patterns/knowledge-based-agent.ts +0 -735
- package/examples/advanced-patterns/persistent-onboarding.ts +0 -728
- package/examples/advanced-patterns/route-lifecycle-hooks.ts +0 -556
- package/examples/advanced-patterns/streaming-responses.ts +0 -656
- package/examples/ai-providers/anthropic-integration.ts +0 -388
- package/examples/ai-providers/openai-integration.ts +0 -228
- package/examples/condition-patterns/function-only-conditions.ts +0 -365
- package/examples/condition-patterns/mixed-array-conditions.ts +0 -477
- package/examples/condition-patterns/route-skipif-patterns.ts +0 -468
- package/examples/condition-patterns/step-skipif-patterns.ts +0 -0
- package/examples/condition-patterns/string-only-conditions.ts +0 -296
- package/examples/conversation-flows/completion-transitions.ts +0 -318
- package/examples/core-concepts/basic-agent.ts +0 -503
- package/examples/core-concepts/modern-streaming-api.ts +0 -309
- package/examples/core-concepts/schema-driven-extraction.ts +0 -332
- package/examples/core-concepts/session-management.ts +0 -494
- package/examples/integrations/database-integration.ts +0 -631
- package/examples/integrations/healthcare-integration.ts +0 -595
- package/examples/integrations/search-integration.ts +0 -530
- package/examples/integrations/server-session-management.ts +0 -307
- package/examples/persistence/custom-adapter.ts +0 -526
- package/examples/persistence/database-persistence.ts +0 -583
- package/examples/persistence/memory-sessions.ts +0 -495
- package/examples/persistence/prisma-schema.example.prisma +0 -74
- package/examples/persistence/redis-persistence.ts +0 -488
- package/examples/tools/basic-tools.ts +0 -765
- package/examples/tools/data-enrichment-tools.ts +0 -593
- package/examples/tools/enhanced-tool-metadata.ts +0 -268
- package/examples/tools/streaming-tool-execution.ts +0 -283
- package/src/core/BatchExecutor.ts +0 -1187
- package/src/core/BatchPromptBuilder.ts +0 -299
- package/src/core/Route.ts +0 -678
- package/src/types/route.ts +0 -392
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Turn pipeline"
|
|
3
|
+
description: "How a single call to agent.respond moves through directive resolution, routing, signals, hooks, the LLM, and persistence."
|
|
4
|
+
type: concept
|
|
5
|
+
order: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Turn pipeline
|
|
9
|
+
|
|
10
|
+
> **Where this is introduced:** [Architecture](./architecture.md)
|
|
11
|
+
|
|
12
|
+
Every interaction with `@falai/agent` is a *turn* — one user message in,
|
|
13
|
+
one assistant message out. Inside that boundary the framework runs a
|
|
14
|
+
fixed sequence of phases: it consumes a pending directive, evaluates
|
|
15
|
+
pre-signals in parallel with routing, picks the next step, runs hooks
|
|
16
|
+
around an LLM call, applies the merged result, and persists. The order
|
|
17
|
+
is the same on every turn. The shape of the turn is the framework. The
|
|
18
|
+
LLM understands; the pipeline keeps the code in control.
|
|
19
|
+
|
|
20
|
+
This page is the per-turn mental model: the diagram, the resolution
|
|
21
|
+
precedence, the per-turn **directive bus**, and the merge rules used
|
|
22
|
+
when more than one handler tries to write at once.
|
|
23
|
+
|
|
24
|
+
## The pipeline diagram
|
|
25
|
+
|
|
26
|
+
```mermaid
|
|
27
|
+
graph TB
|
|
28
|
+
IN[respond / respondStream]
|
|
29
|
+
IN --> PEND{session.pendingDirective?}
|
|
30
|
+
|
|
31
|
+
PEND -- yes --> APPLY1[Apply pending directive]
|
|
32
|
+
APPLY1 --> STEP
|
|
33
|
+
|
|
34
|
+
PEND -- no --> PAR[Parallel]
|
|
35
|
+
PAR --> PRE[PRE-SIGNAL phase<br/>pre / both signals]
|
|
36
|
+
PAR --> ROUTER[AI routing<br/>FlowRouter]
|
|
37
|
+
|
|
38
|
+
PRE --> MERGE{Pre-signal directive?}
|
|
39
|
+
ROUTER --> MERGE
|
|
40
|
+
MERGE -- halt --> HALT[Skip LLM]
|
|
41
|
+
MERGE -- position --> APPLY2[Apply signal position]
|
|
42
|
+
MERGE -- augment only --> KEEP[Keep routing + apply augmentation]
|
|
43
|
+
MERGE -- none --> KEEP
|
|
44
|
+
|
|
45
|
+
APPLY2 --> STEP
|
|
46
|
+
KEEP --> STEP
|
|
47
|
+
|
|
48
|
+
STEP[Step resolution]
|
|
49
|
+
STEP --> AUTO[Auto-step chain]
|
|
50
|
+
AUTO --> BRANCH[step.branches]
|
|
51
|
+
BRANCH --> SUCC[Linear successor /<br/>AI step selection]
|
|
52
|
+
|
|
53
|
+
SUCC --> ENTER[onEnter / prepare hooks<br/>pre-LLM bus]
|
|
54
|
+
HALT --> POSTSIG
|
|
55
|
+
ENTER --> LLM[LLM call + tool loop]
|
|
56
|
+
LLM --> FIN[finalize hook<br/>post-LLM bus]
|
|
57
|
+
FIN --> COL[Collect + merge directives]
|
|
58
|
+
|
|
59
|
+
COL --> POSTSIG[POST-SIGNAL phase<br/>post / both signals]
|
|
60
|
+
POSTSIG --> PERSIST[Persist session]
|
|
61
|
+
PERSIST --> OUT[AgentResponse]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Three things to notice:
|
|
65
|
+
|
|
66
|
+
- **`pendingDirective` shortcuts the top half.** When a previous turn
|
|
67
|
+
left a directive on the session, or `agent.dispatch()` was called
|
|
68
|
+
between turns, it is applied first and routing is skipped.
|
|
69
|
+
- **Pre-signals run in parallel with routing.** Both calls are issued
|
|
70
|
+
via `Promise.all` so the common case (no halt, no signal redirect)
|
|
71
|
+
pays no extra latency. If a pre-signal halts or sets a position
|
|
72
|
+
field, the parallel routing result is discarded.
|
|
73
|
+
- **Post-signals come after the LLM.** They cannot stop *this* turn —
|
|
74
|
+
they observe the assistant's reply, optionally extract structured
|
|
75
|
+
data, and at most arm `pendingDirective` for the next turn.
|
|
76
|
+
|
|
77
|
+
The two signal phases are no-ops when `agent.signals` is empty or
|
|
78
|
+
unset. See [Signals](../reference/signals.md) for the full surface.
|
|
79
|
+
|
|
80
|
+
## Resolution precedence
|
|
81
|
+
|
|
82
|
+
When more than one source could decide where the conversation goes
|
|
83
|
+
next — a pending directive, a pre-signal, the AI router, an auto-step,
|
|
84
|
+
a `step.branches` entry, the linear chain, the post-signal phase — the
|
|
85
|
+
pipeline resolves them in a fixed, locked order. This order does not
|
|
86
|
+
change between releases. Knowing it is enough to predict what every
|
|
87
|
+
turn will do.
|
|
88
|
+
|
|
89
|
+
1. **`session.pendingDirective` is consumed first.**
|
|
90
|
+
Set on the previous turn (e.g. by a post-signal, by a
|
|
91
|
+
`complete: { next }` chain, by a tool that emitted `goTo`) or by
|
|
92
|
+
`agent.dispatch()` from outside any turn. When present it is
|
|
93
|
+
applied verbatim and the rest of the top half is skipped. The
|
|
94
|
+
field is cleared as part of the apply step so it cannot fire
|
|
95
|
+
twice.
|
|
96
|
+
|
|
97
|
+
2. **PRE-SIGNAL phase, in parallel with routing.**
|
|
98
|
+
Pre-phase signals (`phase: 'pre'` or `phase: 'both'`) run via the
|
|
99
|
+
same `Promise.all` that issues the routing classifier call. Their
|
|
100
|
+
directives merge through the per-turn bus (see below). If the
|
|
101
|
+
merged pre-phase directive sets `halt: true`, the LLM is skipped
|
|
102
|
+
for this turn. If it carries a position field (`goTo`,
|
|
103
|
+
`goToStep`, `complete`, `abort`, `reset`), that position wins and
|
|
104
|
+
the routing result is discarded. If it only carries augmentation
|
|
105
|
+
(`appendPrompt`, `injectTools`) or state writes (`dataUpdate`,
|
|
106
|
+
`contextUpdate`), routing is kept and the augmentation is layered
|
|
107
|
+
on top.
|
|
108
|
+
|
|
109
|
+
3. **AI routing.**
|
|
110
|
+
`FlowRouter.decide` picks the active flow and entry step based on
|
|
111
|
+
the user message, conversation history, and each flow's `when`
|
|
112
|
+
condition. Used only when steps 1 and 2 produced no position
|
|
113
|
+
field. Routing is the framework's *intent classifier*: it answers
|
|
114
|
+
"what is the user trying to do right now?" and nothing else. It
|
|
115
|
+
never writes data and never speaks.
|
|
116
|
+
|
|
117
|
+
4. **Auto-step chain.**
|
|
118
|
+
With a current flow and step in hand, the pipeline walks the
|
|
119
|
+
`auto: true` chain — each auto-step's `onEnter` and `prepare`
|
|
120
|
+
hooks fire, branches resolve, and the chain advances without an
|
|
121
|
+
LLM call until it reaches a non-auto step, a `halt`, a `reply`,
|
|
122
|
+
a `complete`, or the per-turn cap (`maxAutoStepsPerTurn`). Auto
|
|
123
|
+
steps are how the code half of the contract advances state in
|
|
124
|
+
bulk between user messages.
|
|
125
|
+
|
|
126
|
+
5. **Step branches.**
|
|
127
|
+
When the resolved step has `branches`, they evaluate in
|
|
128
|
+
declaration order. The `if` predicate runs first (free, code-only
|
|
129
|
+
evaluation). If it passes, the optional `when` string is sent to
|
|
130
|
+
the AI as a yes/no classifier. The first entry whose conditions
|
|
131
|
+
all match wins; its `then` is applied — a step id (jump within
|
|
132
|
+
the flow), a flow id (cross-flow jump), or a full `Directive`.
|
|
133
|
+
Code-only branches incur zero token cost.
|
|
134
|
+
|
|
135
|
+
6. **Linear successor / AI step selection.**
|
|
136
|
+
When no branch matched, the pipeline falls through to the linear
|
|
137
|
+
chain. If exactly one candidate successor passes its `skip`
|
|
138
|
+
condition, that step is entered. If several pass, the framework
|
|
139
|
+
asks the AI to pick (the same routing primitive, scoped to the
|
|
140
|
+
surviving candidates). If none pass, the flow is implicitly
|
|
141
|
+
complete (the *last step terminates the flow* rule from v2 — no
|
|
142
|
+
sentinel value, no special return type).
|
|
143
|
+
|
|
144
|
+
7. **POST-SIGNAL phase.**
|
|
145
|
+
After `finalize` and the post-LLM bus merge, post-phase signals
|
|
146
|
+
evaluate sequentially against the just-completed turn. They see
|
|
147
|
+
the assistant's reply, the collected data, and any tool results.
|
|
148
|
+
Post-phase position directives (`goTo`, `goToStep`, `complete`)
|
|
149
|
+
set `session.pendingDirective` for the *next* turn — there is no
|
|
150
|
+
mid-turn re-entry, by design, to keep turn semantics legible.
|
|
151
|
+
Pre-LLM-only fields (`appendPrompt`, `injectTools`, `halt`) are
|
|
152
|
+
dropped with a debug warning if a post-phase signal emits them.
|
|
153
|
+
|
|
154
|
+
This is the precedence the rest of the framework is built around. The
|
|
155
|
+
[Resolution precedence section](../reference/signals.md#resolution-precedence-within-a-turn)
|
|
156
|
+
on the signals reference page restates the same list as a contract;
|
|
157
|
+
this concept page is the prose explanation.
|
|
158
|
+
|
|
159
|
+
## The directive bus
|
|
160
|
+
|
|
161
|
+
Hooks, tools, and signal handlers all express their intent the same
|
|
162
|
+
way: by emitting a [`Directive`](../reference/directive.md). The
|
|
163
|
+
pipeline collects these emissions into a per-turn, in-memory **bus**
|
|
164
|
+
and reduces them to a single applied directive at the end of each
|
|
165
|
+
phase. Two phases, one merge function.
|
|
166
|
+
|
|
167
|
+
### Pre-LLM phase
|
|
168
|
+
|
|
169
|
+
Sources, in fixed order:
|
|
170
|
+
|
|
171
|
+
1. `agent.hooks.onEnter` (if the agent supports hooks at this level)
|
|
172
|
+
2. `flow.hooks.onEnter` for the active flow
|
|
173
|
+
3. `step.hooks.onEnter` for the resolved step
|
|
174
|
+
4. `step.hooks.prepare`
|
|
175
|
+
5. Any `ctx.dispatch` calls made inside the above hooks
|
|
176
|
+
|
|
177
|
+
These return `PreDirective` — the same shape as `Directive` plus the
|
|
178
|
+
three pre-LLM-only fields (`appendPrompt`, `injectTools`, `halt`). The
|
|
179
|
+
merged result feeds the prompt composer and tool manager before the
|
|
180
|
+
LLM call:
|
|
181
|
+
|
|
182
|
+
- `halt: true` skips the LLM entirely (a `reply`, if any, becomes the
|
|
183
|
+
literal assistant message).
|
|
184
|
+
- `appendPrompt[]` is concatenated into the system prompt for this
|
|
185
|
+
turn only.
|
|
186
|
+
- `injectTools[]` is added to the available tool list for this turn
|
|
187
|
+
only.
|
|
188
|
+
- Position fields, state writes, and `reply` carry forward exactly
|
|
189
|
+
as they would from any directive.
|
|
190
|
+
|
|
191
|
+
### Post-LLM phase
|
|
192
|
+
|
|
193
|
+
Sources, in fixed order:
|
|
194
|
+
|
|
195
|
+
1. Each `ToolResult.directive` returned during the tool loop
|
|
196
|
+
2. Any `ctx.dispatch` calls inside tool handlers
|
|
197
|
+
3. `step.hooks.finalize`
|
|
198
|
+
4. `flow.hooks.onComplete` (when the flow finished this turn)
|
|
199
|
+
5. The `then` branch of any matched `step.branches` entry whose `then`
|
|
200
|
+
was a full `Directive` (not just a step id)
|
|
201
|
+
|
|
202
|
+
These return plain `Directive`. PreDirective-only fields here are
|
|
203
|
+
dropped with a debug warning — `halt` after the fact has no meaning,
|
|
204
|
+
and `appendPrompt` / `injectTools` could not influence a call that
|
|
205
|
+
has already happened.
|
|
206
|
+
|
|
207
|
+
### Why a bus
|
|
208
|
+
|
|
209
|
+
Two reasons. First, multiple emitters are normal: a `prepare` hook
|
|
210
|
+
might add a sentence to the prompt while a tool result writes
|
|
211
|
+
collected data while a `finalize` hook completes the flow. The bus
|
|
212
|
+
makes "who wrote what" explicit and the merge rules deterministic.
|
|
213
|
+
Second, observability: `AgentResponse.directiveChain` returns the full
|
|
214
|
+
list of emitted directives in order with their sources, so traces
|
|
215
|
+
explain themselves without bisecting hook code.
|
|
216
|
+
|
|
217
|
+
The bus is purely in-memory and lasts one turn. Nothing about the bus
|
|
218
|
+
itself is persisted; only the *applied* directive's effects (state
|
|
219
|
+
writes, position changes, `pendingDirective` for the next turn) cross
|
|
220
|
+
the persistence boundary.
|
|
221
|
+
|
|
222
|
+
## Algorithm 4 — merge rules
|
|
223
|
+
|
|
224
|
+
When the bus has more than one directive in a phase, the pipeline
|
|
225
|
+
folds them into a single `Directive` (or `PreDirective` in the pre-LLM
|
|
226
|
+
phase) using the following rules. Same rules in both phases; the
|
|
227
|
+
pre-LLM phase additionally folds the three augmentation fields.
|
|
228
|
+
|
|
229
|
+
### Position fields — winner-takes-all by precedence
|
|
230
|
+
|
|
231
|
+
Exactly one position field can apply per phase. The winner is chosen
|
|
232
|
+
by the precedence:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
abort > complete > goTo / goToStep > reset
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
- **`abort` always wins.** A handler that aborts the conversation
|
|
239
|
+
cannot be overridden by a later "go somewhere else" — there is no
|
|
240
|
+
somewhere else.
|
|
241
|
+
- **`complete` beats `goTo` / `goToStep`.** The flow is ending; any
|
|
242
|
+
follow-up jump belongs in `complete.next`, not as a competing
|
|
243
|
+
position field.
|
|
244
|
+
- **`goTo` and `goToStep` share a tier.** `goTo` is the cross-flow
|
|
245
|
+
hop; `goToStep` is the within-flow hop. Both express "next position
|
|
246
|
+
is here." Among same-tier emissions, last-wins.
|
|
247
|
+
- **`reset` is lowest.** Any explicit jump out of the current flow
|
|
248
|
+
beats a "restart this flow" emitted earlier in the phase.
|
|
249
|
+
|
|
250
|
+
Within the same precedence tier, **last emission wins**. The pipeline
|
|
251
|
+
logs a debug-level warning naming all conflicting sources so a noisy
|
|
252
|
+
turn is diagnosable.
|
|
253
|
+
|
|
254
|
+
### `reply` — last-wins
|
|
255
|
+
|
|
256
|
+
`reply` is a verbatim assistant utterance — the LLM is bypassed for
|
|
257
|
+
the message body. If two emitters set `reply`, the second wins. The
|
|
258
|
+
pipeline logs a debug warning so the override is visible.
|
|
259
|
+
|
|
260
|
+
`reply` and `abort` are mutually exclusive at apply time: an aborted
|
|
261
|
+
conversation cannot deliver a reply, and the pipeline rejects the
|
|
262
|
+
combination as a `FlowConfigurationError`.
|
|
263
|
+
|
|
264
|
+
### `dataUpdate` and `contextUpdate` — shallow-merge in emit order
|
|
265
|
+
|
|
266
|
+
State writes are *additive*: every emitter contributes its slice and
|
|
267
|
+
the merger shallow-merges them in declaration order, last write wins
|
|
268
|
+
on key collision. The `Object.assign({}, a, b)` semantics — top-level
|
|
269
|
+
keys overwrite, nested objects are not deep-merged.
|
|
270
|
+
|
|
271
|
+
This is the rule that lets a flow-level `onEnter` set a default while
|
|
272
|
+
a step-level `prepare` overrides one field on top, without the two
|
|
273
|
+
hooks needing to know about each other.
|
|
274
|
+
|
|
275
|
+
After merging, the combined `dataUpdate` is validated against
|
|
276
|
+
`agent.schema` *atomically* — every field across every emitter is
|
|
277
|
+
checked together, and the session is not mutated unless the whole set
|
|
278
|
+
passes. A failure throws `DataValidationError` with the offending
|
|
279
|
+
field and emitter listed.
|
|
280
|
+
|
|
281
|
+
### `appendPrompt` and `injectTools` — concatenate, then dedupe
|
|
282
|
+
|
|
283
|
+
Pre-LLM only. Both fields are arrays; the merger concatenates them in
|
|
284
|
+
emit order and then deduplicates:
|
|
285
|
+
|
|
286
|
+
- **`appendPrompt`** is concatenated and rendered into the prompt's
|
|
287
|
+
per-turn appendage slot. No deduplication — duplicates from
|
|
288
|
+
different sources are preserved (a flow-level "be polite" plus a
|
|
289
|
+
step-level "be polite" is acceptable redundancy).
|
|
290
|
+
- **`injectTools`** is concatenated and deduped by `Tool.id`. When
|
|
291
|
+
two emitters inject a tool with the same id, the *later* definition
|
|
292
|
+
wins — typically a step-level injection overriding a flow-level
|
|
293
|
+
default.
|
|
294
|
+
|
|
295
|
+
Both arrays apply only for this turn; they are stripped before
|
|
296
|
+
`session.pendingDirective` is written, and they cannot be persisted.
|
|
297
|
+
|
|
298
|
+
### `halt` — logical-OR
|
|
299
|
+
|
|
300
|
+
Pre-LLM only. If *any* pre-phase emitter set `halt: true`, the merged
|
|
301
|
+
directive halts. There is no "vote" — a single emitter is enough. The
|
|
302
|
+
LLM is not called; if a `reply` is also set, that becomes the literal
|
|
303
|
+
assistant message; otherwise the turn ends with an empty body and
|
|
304
|
+
`stoppedReason: 'halt'`.
|
|
305
|
+
|
|
306
|
+
`halt` is the framework's circuit breaker: a hook that detects an
|
|
307
|
+
unresolvable state can stop the turn outright without competing with
|
|
308
|
+
other emitters.
|
|
309
|
+
|
|
310
|
+
## One turn end-to-end
|
|
311
|
+
|
|
312
|
+
The same shape, traced as a sequence. Lanes are *User*, *Agent*
|
|
313
|
+
(`agent.respond`), *Pipeline* (the internal turn pipeline),
|
|
314
|
+
*Provider* (the AI), and *Adapter* (the persistence layer).
|
|
315
|
+
|
|
316
|
+
```mermaid
|
|
317
|
+
sequenceDiagram
|
|
318
|
+
participant User
|
|
319
|
+
participant Agent
|
|
320
|
+
participant Pipeline
|
|
321
|
+
participant Provider
|
|
322
|
+
participant Adapter
|
|
323
|
+
|
|
324
|
+
User->>Agent: respond("I want to book a hotel")
|
|
325
|
+
Agent->>Adapter: load session
|
|
326
|
+
Adapter-->>Agent: SessionState (with pendingDirective?)
|
|
327
|
+
|
|
328
|
+
alt session.pendingDirective is set
|
|
329
|
+
Agent->>Pipeline: applyDirective(pendingDirective)
|
|
330
|
+
Pipeline->>Pipeline: clear pendingDirective
|
|
331
|
+
else
|
|
332
|
+
par Parallel
|
|
333
|
+
Agent->>Pipeline: runPreSignalPhase()
|
|
334
|
+
Pipeline->>Provider: classifier call (batched signals)
|
|
335
|
+
Provider-->>Pipeline: matched + extracted
|
|
336
|
+
and
|
|
337
|
+
Agent->>Pipeline: FlowRouter.decide()
|
|
338
|
+
Pipeline->>Provider: routing call
|
|
339
|
+
Provider-->>Pipeline: { flow, step }
|
|
340
|
+
end
|
|
341
|
+
Pipeline->>Pipeline: merge pre-signal bus<br/>halt? position? augment? none?
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
Pipeline->>Pipeline: walk auto-step chain
|
|
345
|
+
Pipeline->>Pipeline: evaluate step.branches
|
|
346
|
+
Pipeline->>Pipeline: select successor / linear chain
|
|
347
|
+
|
|
348
|
+
Pipeline->>Pipeline: onEnter + prepare hooks<br/>(pre-LLM bus)
|
|
349
|
+
Pipeline->>Provider: generate(prompt + tools + history)
|
|
350
|
+
|
|
351
|
+
loop tool loop
|
|
352
|
+
Provider-->>Pipeline: tool call
|
|
353
|
+
Pipeline->>Pipeline: ToolManager.execute<br/>(may emit Directive)
|
|
354
|
+
Pipeline->>Provider: tool result
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
Provider-->>Pipeline: assistant message
|
|
358
|
+
Pipeline->>Pipeline: finalize hook<br/>(post-LLM bus)
|
|
359
|
+
Pipeline->>Pipeline: collect + merge directives<br/>(Algorithm 4)
|
|
360
|
+
Pipeline->>Pipeline: applyDirective(merged)
|
|
361
|
+
|
|
362
|
+
Pipeline->>Pipeline: runPostSignalPhase()
|
|
363
|
+
Pipeline->>Provider: extraction call (if signals declared)
|
|
364
|
+
Provider-->>Pipeline: matched + extracted
|
|
365
|
+
Pipeline->>Pipeline: post-phase position?<br/>arm pendingDirective for next turn
|
|
366
|
+
|
|
367
|
+
Pipeline->>Adapter: persist session
|
|
368
|
+
Pipeline-->>Agent: AgentResponse
|
|
369
|
+
Agent-->>User: assistant message
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
A few things this diagram makes precise that the high-level graph
|
|
373
|
+
elides:
|
|
374
|
+
|
|
375
|
+
- The session is loaded *before* the pending-directive check, because
|
|
376
|
+
`pendingDirective` lives on the session itself.
|
|
377
|
+
- The pre-signal classifier call and the routing call are issued in
|
|
378
|
+
parallel; the merge step decides whether the routing result is used
|
|
379
|
+
or discarded.
|
|
380
|
+
- The tool loop is internal to the LLM phase. Tools that emit
|
|
381
|
+
directives feed the *post-LLM* bus, not the pre-LLM one — they ran
|
|
382
|
+
during a call, not before it.
|
|
383
|
+
- The post-signal phase happens *after* directive apply, before
|
|
384
|
+
persistence. That's the only window in which post-phase handlers
|
|
385
|
+
can see the fully-applied turn state.
|
|
386
|
+
- Persistence is the last write. Anything that did not survive the
|
|
387
|
+
applied directive (the bus contents, the pre-LLM augmentation
|
|
388
|
+
arrays, `halt`) is gone by the time the adapter is called.
|
|
389
|
+
|
|
390
|
+
## Where to go next
|
|
391
|
+
|
|
392
|
+
The pipeline is the *what happens*. The directive is the *how
|
|
393
|
+
handlers ask for things to happen*. The next concept page covers the
|
|
394
|
+
flat shape, the position field rules, the inheritance chain
|
|
395
|
+
`Directive → PreDirective → SignalDirective`, and the `flow`
|
|
396
|
+
namespace helpers (`flow.isDirective`, `flow.merge`, `flow.validate`)
|
|
397
|
+
that make the bus introspectable from user code.
|
|
398
|
+
|
|
399
|
+
**Next:** [Directives](./directives.md)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Branching"
|
|
3
|
+
description: "Add an explicit, source-local fork to a step with `branches`, mixing code predicates and AI conditions and pointing at step ids, flow ids, or full directives."
|
|
4
|
+
type: guide
|
|
5
|
+
order: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Branching
|
|
9
|
+
|
|
10
|
+
Most flows are linear: ask, collect, confirm, complete. A few aren't. Sometimes the conversation arrives at a step that has to choose between three or four next moves — and the choice is the point of the step, not an aside. This guide shows how to express that fork with `step.branches`.
|
|
11
|
+
|
|
12
|
+
You already know the [`when` / `if` split](./conditions.md): `when` is an AI-evaluated string, `if` is a code predicate, and the two compose so that code runs first for free and the AI only fires when the predicate already passed. `branches` reuses that vocabulary in a list-shaped form. Each entry says "here is a possible next step, and here is how to choose it." Entries evaluate top-to-bottom; the first one whose conditions pass wins.
|
|
13
|
+
|
|
14
|
+
By the end of this page you will have written four branch points: a pure-code fork that costs zero tokens, an AI-only fork that classifies intent, a combined fork that short-circuits the LLM call, and a cross-flow fork that hands control to a different flow with state carry.
|
|
15
|
+
|
|
16
|
+
## The shape
|
|
17
|
+
|
|
18
|
+
`step.branches` is an array of `BranchEntry` objects. Each entry has up to four fields:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
interface BranchEntry<TContext, TData> {
|
|
22
|
+
/** AI-evaluated condition. String or array of strings (AND). */
|
|
23
|
+
when?: string | string[];
|
|
24
|
+
|
|
25
|
+
/** Code predicate. Function or array of functions (AND). */
|
|
26
|
+
if?: BranchPredicate<TContext, TData> | BranchPredicate<TContext, TData>[];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Where to go when this entry matches. One of:
|
|
30
|
+
* - A string matching a step id in the current flow.
|
|
31
|
+
* - A string matching a flow id (sugar for `{ goTo: <flowId> }`).
|
|
32
|
+
* - A full Directive (cross-flow step targets, state writes, completion).
|
|
33
|
+
*/
|
|
34
|
+
then: string | Directive<TContext, TData>;
|
|
35
|
+
|
|
36
|
+
/** Optional label surfaced in event traces and flow visualization. */
|
|
37
|
+
label?: string;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Three rules govern how a `branches` array is read:
|
|
42
|
+
|
|
43
|
+
1. **Declaration order is evaluation order.** The first entry whose conditions pass wins. Later entries don't run.
|
|
44
|
+
2. **Code-first short-circuit.** When an entry has both `if` and `when`, `if` runs first. `when` is only evaluated when `if` passes — saving the LLM call when the predicate already disqualifies the entry.
|
|
45
|
+
3. **An entry with neither `when` nor `if` is an unconditional fallback.** It's only legal as the last entry in the array. Putting it earlier is a `FlowConfigurationError` because every later entry would be unreachable.
|
|
46
|
+
|
|
47
|
+
If no entry matches, branches return `undefined` and resolution falls through to the linear `nextStep` chain or AI step selection — exactly as if `branches` were absent. That is the same fall-through that backs the implicit-fork pattern, so the two forms compose cleanly.
|
|
48
|
+
|
|
49
|
+
## A code-only fork
|
|
50
|
+
|
|
51
|
+
The cheapest fork is one that doesn't call the LLM at all. When the choice is purely a function of `data` and `context`, every entry uses `if` and the whole decision runs in pure TypeScript.
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
flow({
|
|
55
|
+
id: "plan_routing",
|
|
56
|
+
steps: [
|
|
57
|
+
{
|
|
58
|
+
id: "route_by_plan",
|
|
59
|
+
auto: true,
|
|
60
|
+
branches: [
|
|
61
|
+
{ if: ({ data }) => data.plan === "enterprise", then: "enterprise_path", label: "enterprise" },
|
|
62
|
+
{ if: ({ data }) => data.plan === "pro", then: "pro_path", label: "pro" },
|
|
63
|
+
{ then: "free_path" }, // unconditional fallback (last entry)
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{ id: "enterprise_path", prompt: "A specialist will reach out." },
|
|
67
|
+
{ id: "pro_path", prompt: "Set up your pro account." },
|
|
68
|
+
{ id: "free_path", prompt: "Welcome to the free tier." },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Two things matter here. First, `auto: true` on the source step removes the LLM call that would otherwise run for `route_by_plan` itself — the step asks no question, only routes. Combined with code-only branches, the entire decision is a pure compute node in the graph. Second, the unconditional fallback at the end is what guarantees a target. Without it, a `data.plan` value the engine doesn't know about would fall through to linear succession instead of landing on `free_path`.
|
|
74
|
+
|
|
75
|
+
The `BranchPredicate` receives `{ data, context, session, history }`. `data` is `Partial<TData>` — predicates have to null-check any field not declared in the source step's `requires`. Fields covered by `requires` are guaranteed present.
|
|
76
|
+
|
|
77
|
+
## An AI-only fork
|
|
78
|
+
|
|
79
|
+
Some forks need intent classification. The user said something; the agent has to decide whether they want to cancel, ask about billing, or get technical help. None of that lives in `data` yet, and writing a regex would lose the point of the framework.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
flow({
|
|
83
|
+
id: "support",
|
|
84
|
+
steps: [
|
|
85
|
+
{
|
|
86
|
+
id: "classify_request",
|
|
87
|
+
prompt: "How can I help?",
|
|
88
|
+
branches: [
|
|
89
|
+
{ when: "user wants to cancel their account", then: "cancel_flow" },
|
|
90
|
+
{ when: "user is asking about billing", then: "billing_flow" },
|
|
91
|
+
{ when: "user is asking a technical question", then: "tech_support" },
|
|
92
|
+
{ then: "general_help" }, // fallback
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{ id: "tech_support", prompt: "What are you running into?" },
|
|
96
|
+
{ id: "general_help", prompt: "I can help with that." },
|
|
97
|
+
// cancel_flow and billing_flow are top-level flows resolved via goTo.
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`when` strings reuse the same machinery as `step.when`. One LLM call evaluates each entry's condition in declaration order; the first match wins. Conditions are written from the agent's perspective — `"user wants to cancel"` reads naturally and matches the prompts the model already speaks. Don't try to compress them into keywords; the AI is doing classification, not pattern matching.
|
|
103
|
+
|
|
104
|
+
The fallback at the end is doing real work too. When the model can't classify the message into any of the three buckets — say the user typed "hi" — `general_help` catches it instead of dropping the user into AI step selection.
|
|
105
|
+
|
|
106
|
+
## Combining `if` and `when`
|
|
107
|
+
|
|
108
|
+
When an entry has both fields set, code runs first and the AI call only fires when the predicate already passed. This is the same short-circuit you saw in the [conditions guide](./conditions.md), now scoped to a single branch entry.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
flow({
|
|
112
|
+
id: "pricing",
|
|
113
|
+
steps: [
|
|
114
|
+
{
|
|
115
|
+
id: "pricing_routing",
|
|
116
|
+
branches: [
|
|
117
|
+
{
|
|
118
|
+
// Free predicate runs first; AI condition only fires when it passes.
|
|
119
|
+
if: ({ data, context }) =>
|
|
120
|
+
data.country === "US" && context.featureFlags.enableUsPricing,
|
|
121
|
+
when: "user is asking about pricing",
|
|
122
|
+
then: "us_pricing",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
when: "user is asking about pricing",
|
|
126
|
+
then: "global_pricing",
|
|
127
|
+
},
|
|
128
|
+
{ then: "general_help" }, // fallback
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
{ id: "us_pricing", prompt: "Here is US pricing." },
|
|
132
|
+
{ id: "global_pricing", prompt: "Here is global pricing." },
|
|
133
|
+
{ id: "general_help", prompt: "I can help with that." },
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
For users outside the US — or with the feature flag off — `if` returns `false` immediately and the entry is skipped without ever evaluating `when`. The second entry then evaluates `when` once and routes to `global_pricing`. A US user with the flag on passes `if`, and only then does the AI call evaluate "user is asking about pricing." When both pass, the entry wins.
|
|
139
|
+
|
|
140
|
+
The cost difference is real. Without the short-circuit, every turn would pay one LLM call to evaluate `when` for every entry that names it. With it, the framework spends tokens on the entries it can't dispose of for free.
|
|
141
|
+
|
|
142
|
+
## Resolving `then`
|
|
143
|
+
|
|
144
|
+
`then` accepts three forms, resolved in this order:
|
|
145
|
+
|
|
146
|
+
1. **A step id in the current flow** — the engine enters that step directly. Same effect as `nextStep: <id>` would have on a linear chain.
|
|
147
|
+
2. **A flow id** — sugar for `applyDirective({ goTo: <flowId> })`. The string is desugared into a flow transition with no data carry.
|
|
148
|
+
3. **A `Directive` object** — applied via `applyDirective` directly. This is the form to use whenever you need anything more than a bare jump: cross-flow steps, data writes, completion, or verbatim replies.
|
|
149
|
+
|
|
150
|
+
When a string matches both a local step id and a flow id (rare; you'd have to name them the same thing), the local step wins. Step ids are scoped to the current flow, so the lookup is unambiguous.
|
|
151
|
+
|
|
152
|
+
A bare string **never** resolves to a step in a different flow, even when that step id is globally unique. Cross-flow step targets require the `Directive` form:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
{
|
|
156
|
+
if: ({ data }) => data.escalate === true,
|
|
157
|
+
then: { goToStep: { step: "priority_intake", flow: "Escalation" } },
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This is intentional. Keeping the string-form lookup confined to "current flow's steps or any flow id" means a glance at the source tells you what the string means without needing to know whether some unrelated flow happens to define a step by the same name. When you mean to cross flows, the `Directive` form makes that explicit at the call site.
|
|
162
|
+
|
|
163
|
+
## A mixed-target router
|
|
164
|
+
|
|
165
|
+
Most real branch points combine forms. One entry tests data, another classifies intent, a third writes state and completes. The `branches` array reads top-to-bottom as a list of cases:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
flow({
|
|
169
|
+
id: "router",
|
|
170
|
+
steps: [
|
|
171
|
+
{
|
|
172
|
+
id: "classify",
|
|
173
|
+
prompt: "How can I help?",
|
|
174
|
+
branches: [
|
|
175
|
+
// Step id in the current flow.
|
|
176
|
+
{ if: ({ data }) => data.tier === "enterprise", then: "enterprise_path" },
|
|
177
|
+
|
|
178
|
+
// Flow id — sugar for { goTo: "CancellationFlow" }.
|
|
179
|
+
{ when: "user wants to cancel", then: "CancellationFlow" },
|
|
180
|
+
|
|
181
|
+
// Full Directive — cross-flow with data carry.
|
|
182
|
+
{
|
|
183
|
+
when: "user is asking about a refund",
|
|
184
|
+
then: { goTo: { flow: "Refund", data: { source: "classify" } } },
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// Cross-flow step reference (Directive required).
|
|
188
|
+
{
|
|
189
|
+
if: ({ data }) => data.escalate === true,
|
|
190
|
+
then: { goToStep: { step: "priority_intake", flow: "Escalation" } },
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// Directive with completion + state write.
|
|
194
|
+
{
|
|
195
|
+
if: ({ data }) => data.shouldComplete === true,
|
|
196
|
+
then: { complete: true, dataUpdate: { closedAt: new Date().toISOString() } },
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
// Unconditional fallback.
|
|
200
|
+
{ then: "default_path" },
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
{ id: "enterprise_path", prompt: "..." },
|
|
204
|
+
{ id: "default_path", prompt: "..." },
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The mix is the point. Code-only entries pay nothing, AI entries pay one classification, the Directive entries reach across flows or write completion state. Everything declarative is in one list at the source step. There is no separate router file, no decision tree spread across `step.when` clauses on five different successors.
|
|
210
|
+
|
|
211
|
+
## Coexisting with the implicit fork
|
|
212
|
+
|
|
213
|
+
Branches don't replace the implicit-fork pattern (multiple successor steps each carrying their own `step.when`). Both forms stay first-class. Choose by intent:
|
|
214
|
+
|
|
215
|
+
- **Implicit fork** when the flow reads as a linear chain that occasionally skips. The fork is incidental — the bulk of the flow is "do A, then B, then maybe C, then D."
|
|
216
|
+
- **Explicit fork (`branches`)** when the source step is a routing point. The fork is the point — the step exists to choose between N targets and the targets are owned by the source.
|
|
217
|
+
|
|
218
|
+
When both are present on the same step, branches take precedence. If a branch entry matches, the linear chain is bypassed entirely. If `branches` returns `undefined` — no entry matched — the linear chain runs as if `branches` were absent.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
flow({
|
|
222
|
+
id: "support",
|
|
223
|
+
steps: [
|
|
224
|
+
{
|
|
225
|
+
id: "intake",
|
|
226
|
+
prompt: "How can I help?",
|
|
227
|
+
// Branches handle the obvious cases up-front.
|
|
228
|
+
branches: [
|
|
229
|
+
{ if: ({ data }) => data.priority === "P0", then: "fast_path" },
|
|
230
|
+
{ when: "user is asking a billing question", then: "billing" },
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
// Linear successors with their own `when` — the implicit-fork pattern.
|
|
234
|
+
// These only run if no branch entry matched on `intake`.
|
|
235
|
+
{ id: "tech", when: "user is asking a technical question", prompt: "..." },
|
|
236
|
+
{ id: "general", prompt: "I can help with that." },
|
|
237
|
+
// ...
|
|
238
|
+
{ id: "fast_path", prompt: "..." },
|
|
239
|
+
{ id: "billing", prompt: "..." },
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The combination here is deliberate. Branches catch the cases that have an explicit short-circuit (P0 priority is in `data`, billing is unambiguous to classify). Everything else falls through to the implicit fork, which the AI step selector picks among. Neither pattern is forced into doing what the other does better.
|
|
245
|
+
|
|
246
|
+
## What you get back
|
|
247
|
+
|
|
248
|
+
When a branch entry's `then` is applied, the response surfaces it the same way it would surface any other position decision. The active step changes, `currentStep.id` reflects the new step, and `appliedInstructions` is recomputed for the new scope. If the entry's `then` was a `Directive` that included `dataUpdate` or `contextUpdate`, those writes are visible on the response too — branches participate in state writes through the same `applyDirective` machinery as tools and hooks.
|
|
249
|
+
|
|
250
|
+
The optional `label` field on each entry doesn't affect resolution; it's surfaced in event traces and in flow visualization tools so you can see which branch fired without having to read the predicate. Use it on entries whose `if`/`when` conditions are long enough to be hard to skim at a glance.
|
|
251
|
+
|
|
252
|
+
## Recap
|
|
253
|
+
|
|
254
|
+
Four moves cover almost every fork you'll write:
|
|
255
|
+
|
|
256
|
+
- **Code-only** when the choice is in `data` or `context`. Free, deterministic, and combinable with `auto: true` for zero-LLM routing nodes.
|
|
257
|
+
- **AI-only** when the choice is intent classification. One LLM call, declared at the source step instead of scattered across target-step `when` clauses.
|
|
258
|
+
- **Combined `if + when`** when both must agree. Code runs first, AI runs only when code passed.
|
|
259
|
+
- **Directive in `then`** when the target is in another flow, when state needs to be written, or when the branch should complete or abort the flow.
|
|
260
|
+
|
|
261
|
+
`branches` doesn't add a new control-flow primitive. It reuses `when`, `if`, and `Directive` in a list shape that makes the fork visible at the source step. Pair it with the implicit-fork pattern when the linear chain is the natural read; pair it with `auto: true` when the routing decision shouldn't speak.
|
|
262
|
+
|
|
263
|
+
**Next:** [Flow control](./flow-control.md)
|