@falai/agent 2.0.1 → 2.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.
Files changed (126) hide show
  1. package/dist/cjs/core/Agent.d.ts +1 -1
  2. package/dist/cjs/core/Agent.js +3 -3
  3. package/dist/cjs/core/Agent.js.map +1 -1
  4. package/dist/cjs/core/AutoChainExecutor.d.ts +2 -2
  5. package/dist/cjs/core/AutoChainExecutor.js +2 -2
  6. package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
  7. package/dist/cjs/core/PromptComposer.d.ts +1 -1
  8. package/dist/cjs/core/PromptComposer.js +2 -2
  9. package/dist/cjs/core/PromptComposer.js.map +1 -1
  10. package/dist/cjs/core/ResponseEngine.d.ts +1 -1
  11. package/dist/cjs/core/ResponseModal.js +6 -6
  12. package/dist/cjs/core/ResponseModal.js.map +1 -1
  13. package/dist/cjs/core/ResponsePipeline.d.ts +2 -2
  14. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  15. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  16. package/dist/cjs/core/SignalProcessor.d.ts +3 -3
  17. package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
  18. package/dist/cjs/core/SignalProcessor.js +1 -1
  19. package/dist/cjs/core/SignalProcessor.js.map +1 -1
  20. package/dist/cjs/core/Step.d.ts +1 -1
  21. package/dist/cjs/core/Step.js +1 -1
  22. package/dist/cjs/core/ToolManager.d.ts +1 -1
  23. package/dist/cjs/core/ToolManager.js +1 -1
  24. package/dist/cjs/core/flow-namespace.d.ts +4 -4
  25. package/dist/cjs/core/flow-namespace.d.ts.map +1 -1
  26. package/dist/cjs/core/flow-namespace.js +14 -25
  27. package/dist/cjs/core/flow-namespace.js.map +1 -1
  28. package/dist/cjs/index.d.ts +3 -3
  29. package/dist/cjs/index.d.ts.map +1 -1
  30. package/dist/cjs/index.js +2 -2
  31. package/dist/cjs/index.js.map +1 -1
  32. package/dist/cjs/types/agent.d.ts +1 -1
  33. package/dist/cjs/types/agent.d.ts.map +1 -1
  34. package/dist/cjs/types/ai.d.ts +1 -1
  35. package/dist/cjs/types/ai.js +1 -1
  36. package/dist/cjs/types/flow.d.ts +33 -25
  37. package/dist/cjs/types/flow.d.ts.map +1 -1
  38. package/dist/cjs/types/index.d.ts +1 -1
  39. package/dist/cjs/types/index.d.ts.map +1 -1
  40. package/dist/cjs/types/index.js.map +1 -1
  41. package/dist/cjs/types/signals.d.ts +4 -5
  42. package/dist/cjs/types/signals.d.ts.map +1 -1
  43. package/dist/cjs/utils/session.d.ts +1 -1
  44. package/dist/cjs/utils/session.js +4 -4
  45. package/dist/cjs/utils/session.js.map +1 -1
  46. package/dist/core/Agent.d.ts +1 -1
  47. package/dist/core/Agent.js +3 -3
  48. package/dist/core/Agent.js.map +1 -1
  49. package/dist/core/AutoChainExecutor.d.ts +2 -2
  50. package/dist/core/AutoChainExecutor.js +2 -2
  51. package/dist/core/AutoChainExecutor.js.map +1 -1
  52. package/dist/core/PromptComposer.d.ts +1 -1
  53. package/dist/core/PromptComposer.js +2 -2
  54. package/dist/core/PromptComposer.js.map +1 -1
  55. package/dist/core/ResponseEngine.d.ts +1 -1
  56. package/dist/core/ResponseModal.js +6 -6
  57. package/dist/core/ResponseModal.js.map +1 -1
  58. package/dist/core/ResponsePipeline.d.ts +2 -2
  59. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  60. package/dist/core/ResponsePipeline.js.map +1 -1
  61. package/dist/core/SignalProcessor.d.ts +3 -3
  62. package/dist/core/SignalProcessor.d.ts.map +1 -1
  63. package/dist/core/SignalProcessor.js +1 -1
  64. package/dist/core/SignalProcessor.js.map +1 -1
  65. package/dist/core/Step.d.ts +1 -1
  66. package/dist/core/Step.js +1 -1
  67. package/dist/core/ToolManager.d.ts +1 -1
  68. package/dist/core/ToolManager.js +1 -1
  69. package/dist/core/flow-namespace.d.ts +4 -4
  70. package/dist/core/flow-namespace.d.ts.map +1 -1
  71. package/dist/core/flow-namespace.js +14 -25
  72. package/dist/core/flow-namespace.js.map +1 -1
  73. package/dist/index.d.ts +3 -3
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +2 -2
  76. package/dist/index.js.map +1 -1
  77. package/dist/types/agent.d.ts +1 -1
  78. package/dist/types/agent.d.ts.map +1 -1
  79. package/dist/types/ai.d.ts +1 -1
  80. package/dist/types/ai.js +1 -1
  81. package/dist/types/flow.d.ts +33 -25
  82. package/dist/types/flow.d.ts.map +1 -1
  83. package/dist/types/index.d.ts +1 -1
  84. package/dist/types/index.d.ts.map +1 -1
  85. package/dist/types/index.js.map +1 -1
  86. package/dist/types/signals.d.ts +4 -5
  87. package/dist/types/signals.d.ts.map +1 -1
  88. package/dist/utils/session.d.ts +1 -1
  89. package/dist/utils/session.js +4 -4
  90. package/dist/utils/session.js.map +1 -1
  91. package/docs/concepts/architecture.md +19 -26
  92. package/docs/concepts/directives.md +53 -84
  93. package/docs/concepts/pipeline.md +4 -4
  94. package/docs/guides/flow-control.md +12 -13
  95. package/docs/guides/streaming.md +1 -1
  96. package/docs/migration/README.md +1 -1
  97. package/docs/migration/v1-to-v2.md +507 -5
  98. package/docs/reference/adapters.md +1 -1
  99. package/docs/reference/directive.md +9 -10
  100. package/docs/reference/flow.md +3 -3
  101. package/docs/reference/instruction.md +1 -1
  102. package/docs/reference/signals.md +3 -4
  103. package/docs/reference/step.md +9 -10
  104. package/docs/reference/tool.md +1 -1
  105. package/docs/start/02-first-agent.md +1 -2
  106. package/examples/tsconfig.json +1 -3
  107. package/package.json +9 -5
  108. package/src/core/Agent.ts +4 -4
  109. package/src/core/AutoChainExecutor.ts +3 -3
  110. package/src/core/PromptComposer.ts +2 -2
  111. package/src/core/ResponseEngine.ts +1 -1
  112. package/src/core/ResponseModal.ts +18 -18
  113. package/src/core/ResponsePipeline.ts +1 -2
  114. package/src/core/SignalProcessor.ts +7 -7
  115. package/src/core/Step.ts +1 -1
  116. package/src/core/ToolManager.ts +1 -1
  117. package/src/core/flow-namespace.ts +32 -53
  118. package/src/index.ts +2 -3
  119. package/src/types/agent.ts +1 -1
  120. package/src/types/ai.ts +1 -1
  121. package/src/types/flow.ts +41 -26
  122. package/src/types/index.ts +0 -1
  123. package/src/types/signals.ts +4 -5
  124. package/src/utils/session.ts +5 -5
  125. package/docs/migration/route-to-flow.md +0 -561
  126. package/docs/reference/pre-directive.md +0 -131
@@ -66,7 +66,7 @@ interface StepOptions<TContext = unknown, TData = unknown> {
66
66
  | ((ctx: TContext, data?: Partial<TData>) =>
67
67
  void | PrepareResult | Promise<void | PrepareResult>);
68
68
 
69
- // Lifecycle (full — receives HookContext, returns PreDirective / Directive)
69
+ // Lifecycle (full — receives HookContext, returns Directive)
70
70
  hooks?: StepLifecycleHooks<TContext, TData>;
71
71
 
72
72
  // Routing
@@ -75,9 +75,9 @@ interface StepOptions<TContext = unknown, TData = unknown> {
75
75
 
76
76
  interface StepLifecycleHooks<TContext = unknown, TData = unknown> {
77
77
  onEnter?: (ctx: HookContext<TContext, TData>) =>
78
- void | PreDirective<TContext, TData> | Promise<void | PreDirective<TContext, TData>>;
78
+ void | Directive<TContext, TData> | Promise<void | Directive<TContext, TData>>;
79
79
  prepare?: (ctx: HookContext<TContext, TData>) =>
80
- void | PreDirective<TContext, TData> | Promise<void | PreDirective<TContext, TData>>;
80
+ void | Directive<TContext, TData> | Promise<void | Directive<TContext, TData>>;
81
81
  finalize?: (ctx: HookContext<TContext, TData>) =>
82
82
  void | Directive<TContext, TData> | Promise<void | Directive<TContext, TData>>;
83
83
  onExit?: (ctx: HookContext<TContext, TData>, reason: ExitReason) =>
@@ -114,8 +114,8 @@ fields, with a smaller return type.
114
114
 
115
115
  | Hook | When it fires | `hooks.<name>` returns | Top-level shorthand returns | Use it for |
116
116
  |------|---------------|------------------------|------------------------------|------------|
117
- | `onEnter` | Before any other work the first time the step becomes current. | `void \| PreDirective` | n/a (no shorthand) | Append per-turn prompt context, inject one-turn tools, or short-circuit with `halt + reply`. |
118
- | `prepare` | Right before the LLM call, after `onEnter`. | `void \| PreDirective` | `void \| PrepareResult` | Mutate session data, fetch external context, halt the LLM call. |
117
+ | `onEnter` | Before any other work the first time the step becomes current. | `void \| Directive` | n/a (no shorthand) | Append per-turn prompt context, inject one-turn tools, or short-circuit with `halt + reply`. |
118
+ | `prepare` | Right before the LLM call, after `onEnter`. | `void \| Directive` | `void \| PrepareResult` | Mutate session data, fetch external context, halt the LLM call. |
119
119
  | `finalize` | After the LLM call and tool loop complete. | `void \| Directive` | `void \| PrepareResult` | Validate collected data, redirect with `goTo` / `goToStep`, complete the flow. |
120
120
  | `onExit` | When the step is left (next step entered, flow completed, aborted). | `void` | n/a | Emit telemetry. Cannot influence flow control. |
121
121
 
@@ -123,7 +123,7 @@ fields, with a smaller return type.
123
123
  common Directive fields (`dataUpdate`, `contextUpdate`, `goTo`,
124
124
  `goToStep`, `complete`, `halt`, `reply`). Use `hooks.<name>` when you
125
125
  need the full `HookContext` (with `session`, `history`, `dispatch`)
126
- or the full `Directive` / `PreDirective` surface (`appendPrompt`,
126
+ or the full `Directive` surface (`appendPrompt`,
127
127
  `injectTools`, `abort`, `reset`).
128
128
 
129
129
  ### Resolution within a step
@@ -133,7 +133,7 @@ For one step, the engine walks this sequence per turn:
133
133
  1. Evaluate `if` (code, AND) and `when` (AI, AND) — fails skip the step entirely.
134
134
  2. Evaluate `skip` (code, OR) — true means bypass and fall through.
135
135
  3. Check `requires` — refuse entry if any required field is missing.
136
- 4. Run `onEnter`, then `prepare` / `hooks.prepare`. May emit a `PreDirective`.
136
+ 4. Run `onEnter`, then `prepare` / `hooks.prepare`. May emit a `Directive` (pre-LLM fields honored).
137
137
  5. **LLM step**: call the LLM with the step's prompt, tools, and instructions; tool loop runs until completion. **Auto step**: skip the LLM call. **Reply step**: render `reply` as the verbatim assistant message.
138
138
  6. Run `finalize` / `hooks.finalize`. May emit a `Directive`.
139
139
  7. Evaluate `branches`. The first entry whose `if`/`when` passes wins; its `then` resolves to a step id, a flow id, or a full `Directive`. If no entry matches, fall through.
@@ -282,7 +282,7 @@ const agent = createAgent({
282
282
  }),
283
283
  }
284
284
 
285
- // Full hook — receives HookContext, returns PreDirective.
285
+ // Full hook — receives HookContext, returns Directive.
286
286
  {
287
287
  id: 'enrich',
288
288
  auto: true,
@@ -326,14 +326,13 @@ Runtime errors that surface from a step's hook execution include
326
326
 
327
327
  ## Related
328
328
 
329
- - [Architecture](../concepts/architecture.md) — where Step fits among the seven primitives
329
+ - [Architecture](../concepts/architecture.md) — where Step fits among the six primitives
330
330
  - [Turn pipeline](../concepts/pipeline.md) — when each step phase fires inside a turn
331
331
  - [Flow](./flow.md) — the parent type that contains a step's `steps[]` entry
332
332
  - [Tool](./tool.md) — the shape consumed by `tools[]`
333
333
  - [Instruction](./instruction.md) — the shape consumed by `instructions[]`
334
334
  - [Branches](./branches.md) — the full `BranchEntry` / `BranchMap` contract
335
335
  - [Directive](./directive.md) — the post-LLM return type for `hooks.finalize`
336
- - [PreDirective](./pre-directive.md) — the pre-LLM return type for `hooks.onEnter` and `hooks.prepare`
337
336
  - [When and if](../guides/conditions.md) — picking between AI and code conditions
338
337
  - [Branching](../guides/branching.md) — adding source-local forks
339
338
  - [Flow control](../guides/flow-control.md) — redirecting from a hook
@@ -262,7 +262,7 @@ Permission denials (`checkPermissions` returning `allowed: false`) are surfaced
262
262
  ## Related
263
263
 
264
264
  - [Add tools](../start/04-add-tools.md) — tutorial that introduces this type.
265
- - [Architecture](../concepts/architecture.md) — where Tool fits among the seven primitives.
265
+ - [Architecture](../concepts/architecture.md) — where Tool fits among the six primitives.
266
266
  - [Directives](../concepts/directives.md) — what `dispatch` and `directive` emit.
267
267
  - [Directive](./directive.md) — the flat shape used by both forms above.
268
268
  - [Flow control](../guides/flow-control.md) — recipes for redirecting from tools and hooks.
@@ -36,7 +36,7 @@ const response = await agent.respond("Hi, I'm Alice");
36
36
  console.log(response.message);
37
37
  ```
38
38
 
39
- That is the whole program. No persistence config, no tools, no branches, no signals. Three primitives appear by name (`Agent`, `Flow`, `Step`) and four more sit one decision away (`Tool`, `Instruction`, `Directive`, `PreDirective`). The next sections walk through every line.
39
+ That is the whole program. No persistence config, no tools, no branches, no signals. Three primitives appear by name (`Agent`, `Flow`, `Step`) and three more sit one decision away (`Tool`, `Instruction`, `Directive`). The next sections walk through every line.
40
40
 
41
41
  ## Walk it line by line
42
42
 
@@ -139,7 +139,6 @@ Three primitives appear by name in the code above: [`Agent`](../concepts/archite
139
139
  - [`Tool`](../concepts/architecture.md#tool) — a typed function the AI can call. May redirect the conversation by emitting a directive. Added on page [04](./04-add-tools.md).
140
140
  - [`Instruction`](../concepts/architecture.md#instruction) — a `must` / `never` / `should` behavioral statement at agent, flow, or step scope.
141
141
  - [`Directive`](../concepts/architecture.md#directive) — a flat object any tool, hook, or branch returns to write state, change position, or speak verbatim.
142
- - [`PreDirective`](../concepts/architecture.md#predirective) — a `Directive` plus per-turn shaping (`appendPrompt`, `injectTools`, `halt`) returned from `onEnter` and `prepare` hooks.
143
142
 
144
143
  Read [Architecture](../concepts/architecture.md) end to end when you want the full mental model — what each primitive owns, how they reference each other, and why the set is exactly seven.
145
144
 
@@ -7,9 +7,7 @@
7
7
  "DOM"
8
8
  ],
9
9
  "moduleResolution": "bundler",
10
- "types": [
11
- "@types/bun"
12
- ],
10
+ "types": [],
13
11
  "strict": true,
14
12
  "esModuleInterop": true,
15
13
  "skipLibCheck": true,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@falai/agent",
3
- "version": "2.0.1",
4
- "description": "Standalone, strongly-typed AI Agent framework with route DSL and AI provider strategy",
3
+ "version": "2.1.0",
4
+ "description": "Conversational state engine for TypeScript where the AI understands, but the code is in control",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
7
7
  "module": "./dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "bugs": {
37
37
  "url": "https://github.com/falai-dev/agent/issues"
38
38
  },
39
- "homepage": "https://github.com/falai-dev/agent#readme",
39
+ "homepage": "https://falai.dev",
40
40
  "scripts": {
41
41
  "build": "npm run build:esm && npm run build:cjs && npm run fix:cjs",
42
42
  "build:esm": "tsc",
@@ -65,11 +65,15 @@
65
65
  "typescript",
66
66
  "conversational",
67
67
  "gemini",
68
+ "openai",
69
+ "anthropic",
68
70
  "llm",
69
71
  "chatbot",
70
72
  "framework",
71
- "routes",
72
- "dsl"
73
+ "flows",
74
+ "schema",
75
+ "directives",
76
+ "signals"
73
77
  ],
74
78
  "author": "Gustavo Salomé <gusnips>",
75
79
  "license": "MIT",
package/src/core/Agent.ts CHANGED
@@ -1225,7 +1225,7 @@ export class Agent<TContext = any, TData = any> {
1225
1225
  }
1226
1226
  }
1227
1227
 
1228
- // Strip PreDirective-only fields before storing
1228
+ // Strip pre-LLM-only fields before storing
1229
1229
  const stripped = this.stripPreDirectiveFields(directive);
1230
1230
 
1231
1231
  // Set pendingDirective on the session without applying it
@@ -1439,7 +1439,7 @@ export class Agent<TContext = any, TData = any> {
1439
1439
  }
1440
1440
 
1441
1441
  /**
1442
- * Strip PreDirective-only fields (appendPrompt, injectTools, halt) from a directive.
1442
+ * Strip pre-LLM-only fields (appendPrompt, injectTools, halt) from a directive.
1443
1443
  * These fields are transient (one-turn lifetime) and must not be persisted.
1444
1444
  * @private
1445
1445
  */
@@ -1452,8 +1452,8 @@ export class Agent<TContext = any, TData = any> {
1452
1452
  const { appendPrompt, injectTools, halt, ...rest } = raw;
1453
1453
 
1454
1454
  if (appendPrompt || injectTools || halt !== undefined) {
1455
- logger.debug(
1456
- `[Agent] Stripped PreDirective-only fields before storing pendingDirective: ` +
1455
+ logger.warn(
1456
+ `[Agent] Ignoring pre-LLM-only fields on pendingDirective (these only take effect in onEnter/prepare hooks): ` +
1457
1457
  `${[appendPrompt && 'appendPrompt', injectTools && 'injectTools', halt !== undefined && 'halt'].filter(Boolean).join(', ')}`
1458
1458
  );
1459
1459
  }
@@ -25,7 +25,7 @@ import type { StoppedReason } from "../types/flow";
25
25
 
26
26
  /**
27
27
  * The directive-like object that `prepare` may return on auto-steps.
28
- * This is a structural subset of the full PreDirective that v2 will formalize.
28
+ * This is a structural subset of Directive (pre-LLM fields).
29
29
  */
30
30
  export interface AutoStepPrepareResult {
31
31
  dataUpdate?: Record<string, unknown>;
@@ -292,7 +292,7 @@ export class AutoChainExecutor<TContext = unknown, TData = unknown> {
292
292
 
293
293
  /**
294
294
  * Run onEnter and prepare hooks for an auto-step, collecting any
295
- * PreDirective-like return values.
295
+ * Directive return values (pre-LLM fields honored).
296
296
  */
297
297
  private async runStepHooks(
298
298
  step: Step<TContext, TData>,
@@ -310,7 +310,7 @@ export class AutoChainExecutor<TContext = unknown, TData = unknown> {
310
310
  }
311
311
  }
312
312
 
313
- // prepare hook — for auto-steps, prepare may return a PreDirective-like object
313
+ // prepare hook — for auto-steps, prepare may return a Directive with pre-LLM fields
314
314
  if (step.prepare) {
315
315
  if (typeof step.prepare === 'function') {
316
316
  const prepareResult = await (step.prepare as (
@@ -419,7 +419,7 @@ export class PromptComposer<TContext = unknown, TData = unknown> {
419
419
  * Build the final prompt string.
420
420
  *
421
421
  * @param options.transientAppendage - Per-turn sentences from merged
422
- * PreDirective.appendPrompt arrays (outer-to-inner: agent.onEnter →
422
+ * Directive.appendPrompt arrays (outer-to-inner: agent.onEnter →
423
423
  * flow.onEnter → step.onEnter → step.prepare). Appended after all
424
424
  * other sections. Fresh every turn, never cached, never persisted.
425
425
  * **Validates: Requirements 2.2, 2.8, 2.11, 27.1, 27.2, 27.4**
@@ -434,7 +434,7 @@ export class PromptComposer<TContext = unknown, TData = unknown> {
434
434
  sections = this.parts.filter(Boolean);
435
435
  }
436
436
 
437
- // Append transient per-turn sentences (from PreDirective.appendPrompt).
437
+ // Append transient per-turn sentences (from Directive.appendPrompt).
438
438
  // These are never cached — they are a fresh slot built per turn.
439
439
  if (options?.transientAppendage && options.transientAppendage.length > 0) {
440
440
  const appendageBlock = options.transientAppendage.join("\n");
@@ -33,7 +33,7 @@ export interface BuildResponsePromptParams<
33
33
  // NEW: Agent-level schema for data validation
34
34
  agentSchema?: StructuredSchema;
35
35
  /**
36
- * Per-turn transient appendage from merged PreDirective.appendPrompt arrays.
36
+ * Per-turn transient appendage from merged Directive.appendPrompt arrays.
37
37
  * Appended to the system prompt after all other sections.
38
38
  * Fresh every turn, never cached, never persisted.
39
39
  */
@@ -18,7 +18,7 @@ import type {
18
18
  ScopedInstructions,
19
19
  AppliedInstruction,
20
20
  PrepareResult,
21
- PreDirective,
21
+ Directive,
22
22
  } from "../types";
23
23
  import type { SignalFiring } from "../types/signals";
24
24
  import type { Agent } from "./Agent";
@@ -140,7 +140,7 @@ interface ResponseContext<TContext = unknown, TData = unknown> {
140
140
  /** Signal firings accumulated across both phases (pre + post) for the response surface. */
141
141
  signalFirings?: SignalFiring<TContext, TData>[];
142
142
  /** Pre-phase merged directive from signals (non-position fields like appendPrompt, injectTools). */
143
- signalPreDirective?: PreDirective<TContext, TData>;
143
+ signalPreDirective?: Directive<TContext, TData>;
144
144
  /** Whether the pre-signal phase emitted a halt directive. */
145
145
  signalHalted?: boolean;
146
146
  /** Reply from a halt directive. */
@@ -477,7 +477,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
477
477
  session: SessionState<TData>;
478
478
  isFlowComplete: boolean;
479
479
  signalFirings?: SignalFiring<TContext, TData>[];
480
- signalPreDirective?: PreDirective<TContext, TData>;
480
+ signalPreDirective?: Directive<TContext, TData>;
481
481
  signalHalted?: boolean;
482
482
  signalHaltReply?: string;
483
483
  };
@@ -533,7 +533,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
533
533
  /** Signal firings from the pre-phase (threaded through for response surface). */
534
534
  signalFirings?: SignalFiring<TContext, TData>[];
535
535
  /** Non-position signal directive for pre-LLM augmentation (appendPrompt, injectTools, etc). */
536
- signalPreDirective?: PreDirective<TContext, TData>;
536
+ signalPreDirective?: Directive<TContext, TData>;
537
537
  /** Pre-signal phase halted the turn. */
538
538
  signalHalted?: boolean;
539
539
  /** Reply text from the halt directive. */
@@ -752,7 +752,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
752
752
  signalResult: {
753
753
  firings: SignalFiring<TContext, TData>[];
754
754
  updatedSession: SessionState<TData>;
755
- mergedDirective: PreDirective<TContext, TData> | undefined;
755
+ mergedDirective: Directive<TContext, TData> | undefined;
756
756
  },
757
757
  _params: { session: SessionState<TData>; history: Event[]; context: TContext },
758
758
  ): {
@@ -762,7 +762,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
762
762
  session: SessionState<TData>;
763
763
  isFlowComplete: boolean;
764
764
  signalFirings?: SignalFiring<TContext, TData>[];
765
- signalPreDirective?: PreDirective<TContext, TData>;
765
+ signalPreDirective?: Directive<TContext, TData>;
766
766
  signalHalted?: boolean;
767
767
  signalHaltReply?: string;
768
768
  } {
@@ -1381,15 +1381,15 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1381
1381
  historyEvents: Event[];
1382
1382
  signal?: AbortSignal;
1383
1383
  /**
1384
- * Per-turn transient appendage from merged PreDirective.appendPrompt.
1384
+ * Per-turn transient appendage from merged directive.appendPrompt.
1385
1385
  * Fresh every turn, never cached, never persisted.
1386
1386
  */
1387
1387
  transientAppendage?: string[];
1388
1388
  /**
1389
- * Merged PreDirective from the directive bus's pre-LLM phase drain.
1389
+ * Merged directive from the directive bus's pre-LLM phase drain.
1390
1390
  * When `halt: true`, the LLM call is skipped entirely.
1391
1391
  */
1392
- mergedPreDirective?: PreDirective<TContext, TData>;
1392
+ mergedPreDirective?: Directive<TContext, TData>;
1393
1393
  }): Promise<{
1394
1394
  message: string;
1395
1395
  toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
@@ -1503,7 +1503,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1503
1503
  // ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
1504
1504
  // A step with `reply` set emits a verbatim template response without LLM.
1505
1505
  // onEnter and prepare have already fired normally at this point.
1506
- // If prepare returned a PreDirective with `reply`, that overrides
1506
+ // If prepare returned a Directive with `reply`, that overrides
1507
1507
  // the step-declared reply (last-emission-wins per Algorithm 4).
1508
1508
  if (nextStep.reply != null) {
1509
1509
  // Determine the effective reply: prepare-emitted reply wins over step-declared
@@ -1515,7 +1515,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1515
1515
  return { message: effectiveReply, session, stoppedReason: 'reply' };
1516
1516
  }
1517
1517
 
1518
- // Transient appendage: per-turn slot from PreDirective.appendPrompt.
1518
+ // Transient appendage: per-turn slot from Directive.appendPrompt.
1519
1519
  // Fresh each turn, never cached, never persisted.
1520
1520
  // Wrapped in try/finally to ensure cleanup even on abnormal termination.
1521
1521
  let turnTransientAppendage: string[] | undefined = transientAppendage;
@@ -1592,7 +1592,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1592
1592
  return { message, toolCalls, session, appliedInstructions };
1593
1593
  } finally {
1594
1594
  // Drain the transient appendage at end of turn.
1595
- // This ensures PreDirective.appendPrompt does not leak to subsequent
1595
+ // This ensures Directive.appendPrompt does not leak to subsequent
1596
1596
  // turns even when the turn terminates abnormally (error, abort, reject).
1597
1597
  turnTransientAppendage = undefined;
1598
1598
  }
@@ -1799,15 +1799,15 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1799
1799
  historyEvents: Event[];
1800
1800
  signal?: AbortSignal;
1801
1801
  /**
1802
- * Per-turn transient appendage from merged PreDirective.appendPrompt.
1802
+ * Per-turn transient appendage from merged directive.appendPrompt.
1803
1803
  * Fresh every turn, never cached, never persisted.
1804
1804
  */
1805
1805
  transientAppendage?: string[];
1806
1806
  /**
1807
- * Merged PreDirective from the directive bus's pre-LLM phase drain.
1807
+ * Merged directive from the directive bus's pre-LLM phase drain.
1808
1808
  * When `halt: true`, the LLM call is skipped entirely.
1809
1809
  */
1810
- mergedPreDirective?: PreDirective<TContext, TData>;
1810
+ mergedPreDirective?: Directive<TContext, TData>;
1811
1811
  }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1812
1812
  const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
1813
1813
  let session = params.session;
@@ -1918,7 +1918,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1918
1918
  // ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
1919
1919
  // A step with `reply` set emits a verbatim template response without LLM.
1920
1920
  // onEnter and prepare have already fired normally. If prepare returned
1921
- // a PreDirective with `reply`, that overrides the step-declared reply.
1921
+ // a Directive with `reply`, that overrides the step-declared reply.
1922
1922
  if (nextStep.reply != null) {
1923
1923
  const effectiveReply = mergedPreDirective?.reply ?? await render(
1924
1924
  nextStep.reply,
@@ -1937,7 +1937,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1937
1937
  return;
1938
1938
  }
1939
1939
 
1940
- // Transient appendage: per-turn slot from PreDirective.appendPrompt.
1940
+ // Transient appendage: per-turn slot from Directive.appendPrompt.
1941
1941
  // Fresh each turn, never cached, never persisted.
1942
1942
  // Wrapped in try/finally to ensure cleanup even on abnormal termination.
1943
1943
  let turnTransientAppendage: string[] | undefined = transientAppendage;
@@ -2092,7 +2092,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2092
2092
  }
2093
2093
  } finally {
2094
2094
  // Drain the transient appendage at end of turn.
2095
- // This ensures PreDirective.appendPrompt does not leak to subsequent
2095
+ // This ensures Directive.appendPrompt does not leak to subsequent
2096
2096
  // turns even when the turn terminates abnormally (error, abort, reject).
2097
2097
  turnTransientAppendage = undefined;
2098
2098
  }
@@ -9,7 +9,6 @@ import type {
9
9
  AgentStructuredResponse,
10
10
  Tool,
11
11
  Directive,
12
- PreDirective,
13
12
  } from "../types";
14
13
  import type { SignalFiring } from "../types/signals";
15
14
  import type { SignalProcessor } from "./SignalProcessor";
@@ -162,7 +161,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
162
161
  ): Promise<{
163
162
  firings: SignalFiring<TContext, TData>[];
164
163
  updatedSession: SessionState<TData>;
165
- mergedDirective: PreDirective<TContext, TData> | undefined;
164
+ mergedDirective: Directive<TContext, TData> | undefined;
166
165
  }> {
167
166
  if (!this.signalProcessor) {
168
167
  return { firings: [], updatedSession: session, mergedDirective: undefined };
@@ -15,7 +15,7 @@ import type { AiProvider } from "../types/ai";
15
15
  import type { Event } from "../types/history";
16
16
  import { MessageRole as MessageRoleEnum } from "../types/history";
17
17
  import type { SessionState } from "../types/session";
18
- import type { PreDirective, Directive } from "../types/flow";
18
+ import type { Directive } from "../types/flow";
19
19
  import type {
20
20
  Signal,
21
21
  SignalContext,
@@ -295,14 +295,14 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
295
295
 
296
296
  /**
297
297
  * Pre-signal phase. Delegates to `runPhase('pre', ...)`.
298
- * Return type narrows `mergedDirective` to `PreDirective | undefined`.
298
+ * Return type narrows `mergedDirective` to `Directive | undefined`.
299
299
  */
300
300
  async runPreSignalPhase(params: {
301
301
  session: SessionState<TData>;
302
302
  history: Event[];
303
303
  context: TContext;
304
304
  }): Promise<{
305
- mergedDirective?: PreDirective<TContext, TData>;
305
+ mergedDirective?: Directive<TContext, TData>;
306
306
  firings: SignalFiring<TContext, TData>[];
307
307
  updatedSession: SessionState<TData>;
308
308
  }> {
@@ -335,7 +335,7 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
335
335
  phase: 'pre' | 'post',
336
336
  params: { session: SessionState<TData>; history: Event[]; context: TContext },
337
337
  ): Promise<{
338
- mergedDirective?: PreDirective<TContext, TData>;
338
+ mergedDirective?: Directive<TContext, TData>;
339
339
  firings: SignalFiring<TContext, TData>[];
340
340
  updatedSession: SessionState<TData>;
341
341
  }> {
@@ -584,7 +584,7 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
584
584
  }
585
585
 
586
586
  // ── STEP 9: merge directives ─────────────────────────────────────────
587
- let mergedDirective: PreDirective<TContext, TData> | undefined;
587
+ let mergedDirective: Directive<TContext, TData> | undefined;
588
588
 
589
589
  if (bus.length > 0) {
590
590
  mergedDirective = this.mergeDirectives(bus);
@@ -633,8 +633,8 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
633
633
  */
634
634
  private mergeDirectives(
635
635
  directives: SignalDirective<TContext, TData>[],
636
- ): PreDirective<TContext, TData> {
637
- const merged: PreDirective<TContext, TData> = {};
636
+ ): Directive<TContext, TData> {
637
+ const merged: Directive<TContext, TData> = {};
638
638
 
639
639
  for (const d of directives) {
640
640
  // Position fields — last-write-wins
package/src/core/Step.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Step in the route DSL
2
+ * Step in a conversational flow
3
3
  */
4
4
 
5
5
  import type {
@@ -62,7 +62,7 @@ export class ToolManager<TContext = unknown, TData = unknown> {
62
62
  private toolRegistry: Map<string, Tool<TContext, TData>>;
63
63
 
64
64
  /**
65
- * Per-turn transient tool layer populated from PreDirective.injectTools.
65
+ * Per-turn transient tool layer populated from Directive.injectTools.
66
66
  * Resolution order: transient → step → flow → agent.
67
67
  * Drained at end of turn via try/finally guard.
68
68
  *
@@ -25,7 +25,12 @@ const POSITION_PRECEDENCE: Record<PositionField, number> = {
25
25
 
26
26
  // ─── Helpers ─────────────────────────────────────────────────────────────────
27
27
 
28
- function getSetPositionFields(d: Directive): PositionField[] {
28
+ /** Structural type for anything that has position fields (read-only access). */
29
+ type HasPositionFields = {
30
+ readonly [K in PositionField]?: unknown;
31
+ };
32
+
33
+ function getSetPositionFields(d: HasPositionFields): PositionField[] {
29
34
  return POSITION_FIELDS.filter((f) => d[f] !== undefined && d[f] !== null);
30
35
  }
31
36
 
@@ -44,7 +49,7 @@ function beatsCurrent(
44
49
  // ─── Public API ──────────────────────────────────────────────────────────────
45
50
 
46
51
  /**
47
- * Type guard: is `x` a Directive (or any subtype like PreDirective)?
52
+ * Type guard: is `x` a Directive (or any subtype like SignalDirective)?
48
53
  *
49
54
  * A value is considered a Directive if it is a non-null object. The Directive
50
55
  * interface has all-optional fields, so any plain object qualifies structurally.
@@ -65,31 +70,31 @@ function isDirective(x: unknown): x is Directive {
65
70
  * ties broken by emission order (b wins over a — last wins).
66
71
  * - reply: last-wins (b.reply overrides a.reply if set).
67
72
  * - dataUpdate / contextUpdate: shallow-merge (b overrides a on key collision).
68
- * - appendPrompt / injectTools (PreDirective fields): concatenate then dedupe.
73
+ * - appendPrompt / injectTools (pre-LLM fields): concatenate then dedupe.
69
74
  * - halt: logical-OR.
70
75
  */
71
- function merge<T extends Directive>(a: T, b: T): T {
76
+ function merge<TContext, TData>(a: Directive<TContext, TData>, b: Directive<TContext, TData>): Directive<TContext, TData> {
72
77
  const result = {} as Record<string, unknown>;
73
78
 
74
79
  // ── Position field: winner-takes-all by precedence, b wins ties ──
75
- const aPos = getSetPositionFields(a as Directive);
76
- const bPos = getSetPositionFields(b as Directive);
80
+ const aPos = getSetPositionFields(a);
81
+ const bPos = getSetPositionFields(b);
77
82
 
78
83
  // Pick the highest-priority position field across both directives.
79
84
  // b's fields are evaluated after a's, so b wins on same precedence (last-wins).
80
85
  let winnerField: PositionField | null = null;
81
- let winnerSource: Directive | null = null;
86
+ let winnerSource: Directive<TContext, TData> | null = null;
82
87
 
83
88
  for (const field of aPos) {
84
89
  if (beatsCurrent(field, winnerField)) {
85
90
  winnerField = field;
86
- winnerSource = a as Directive;
91
+ winnerSource = a;
87
92
  }
88
93
  }
89
94
  for (const field of bPos) {
90
95
  if (beatsCurrent(field, winnerField)) {
91
96
  winnerField = field;
92
- winnerSource = b as Directive;
97
+ winnerSource = b;
93
98
  }
94
99
  }
95
100
 
@@ -98,54 +103,30 @@ function merge<T extends Directive>(a: T, b: T): T {
98
103
  }
99
104
 
100
105
  // ── reply: last-wins ──
101
- if ((b as Directive).reply !== undefined) {
102
- result.reply = (b as Directive).reply;
103
- } else if ((a as Directive).reply !== undefined) {
104
- result.reply = (a as Directive).reply;
106
+ if (b.reply !== undefined) {
107
+ result.reply = b.reply;
108
+ } else if (a.reply !== undefined) {
109
+ result.reply = a.reply;
105
110
  }
106
111
 
107
112
  // ── dataUpdate: shallow merge ──
108
- const aData = (a as Record<string, unknown>).dataUpdate as
109
- | Record<string, unknown>
110
- | undefined;
111
- const bData = (b as Record<string, unknown>).dataUpdate as
112
- | Record<string, unknown>
113
- | undefined;
114
- if (aData || bData) {
115
- result.dataUpdate = { ...aData, ...bData };
113
+ if (a.dataUpdate || b.dataUpdate) {
114
+ result.dataUpdate = { ...a.dataUpdate, ...b.dataUpdate };
116
115
  }
117
116
 
118
117
  // ── contextUpdate: shallow merge ──
119
- const aCtx = (a as Record<string, unknown>).contextUpdate as
120
- | Record<string, unknown>
121
- | undefined;
122
- const bCtx = (b as Record<string, unknown>).contextUpdate as
123
- | Record<string, unknown>
124
- | undefined;
125
- if (aCtx || bCtx) {
126
- result.contextUpdate = { ...aCtx, ...bCtx };
118
+ if (a.contextUpdate || b.contextUpdate) {
119
+ result.contextUpdate = { ...a.contextUpdate, ...b.contextUpdate };
127
120
  }
128
121
 
129
- // ── appendPrompt (PreDirective): concatenate ──
130
- const aPrompt = (a as Record<string, unknown>).appendPrompt as
131
- | string[]
132
- | undefined;
133
- const bPrompt = (b as Record<string, unknown>).appendPrompt as
134
- | string[]
135
- | undefined;
136
- if (aPrompt || bPrompt) {
137
- result.appendPrompt = [...(aPrompt ?? []), ...(bPrompt ?? [])];
122
+ // ── appendPrompt (pre-LLM): concatenate ──
123
+ if (a.appendPrompt || b.appendPrompt) {
124
+ result.appendPrompt = [...(a.appendPrompt ?? []), ...(b.appendPrompt ?? [])];
138
125
  }
139
126
 
140
- // ── injectTools (PreDirective): concatenate then dedupe by id (last wins) ──
141
- const aTools = (a as Record<string, unknown>).injectTools as
142
- | Array<{ id: string;[k: string]: unknown }>
143
- | undefined;
144
- const bTools = (b as Record<string, unknown>).injectTools as
145
- | Array<{ id: string;[k: string]: unknown }>
146
- | undefined;
147
- if (aTools || bTools) {
148
- const combined = [...(aTools ?? []), ...(bTools ?? [])];
127
+ // ── injectTools (pre-LLM): concatenate then dedupe by id (last wins) ──
128
+ if (a.injectTools || b.injectTools) {
129
+ const combined = [...(a.injectTools ?? []), ...(b.injectTools ?? [])];
149
130
  // Dedupe by id — last definition wins
150
131
  const seen = new Map<string, (typeof combined)[number]>();
151
132
  for (const tool of combined) {
@@ -154,14 +135,12 @@ function merge<T extends Directive>(a: T, b: T): T {
154
135
  result.injectTools = Array.from(seen.values());
155
136
  }
156
137
 
157
- // ── halt (PreDirective): logical OR ──
158
- const aHalt = (a as Record<string, unknown>).halt as boolean | undefined;
159
- const bHalt = (b as Record<string, unknown>).halt as boolean | undefined;
160
- if (aHalt || bHalt) {
138
+ // ── halt (pre-LLM): logical OR ──
139
+ if (a.halt || b.halt) {
161
140
  result.halt = true;
162
141
  }
163
142
 
164
- return result as T;
143
+ return result as Directive<TContext, TData>;
165
144
  }
166
145
 
167
146
  /**
@@ -170,7 +149,7 @@ function merge<T extends Directive>(a: T, b: T): T {
170
149
  * - `goTo` set as empty object `{}` (no flow target).
171
150
  * - `reply` co-existing with `abort` (abort ends the conversation; a reply is nonsensical).
172
151
  */
173
- function validate(d: Directive): void {
152
+ function validate<TContext, TData>(d: Directive<TContext, TData>): void {
174
153
  // ── Multiple position fields ──
175
154
  const setFields = getSetPositionFields(d);
176
155
  if (setFields.length > 1) {
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * @falai/agent - Standalone AI Agent framework
2
+ * @falai/agent Conversational state engine for TypeScript
3
3
  *
4
- * A strongly-typed, modular agent framework with flow DSL and AI provider strategy
4
+ * The AI understands. The code is in control.
5
5
  */
6
6
 
7
7
  // Core
@@ -173,7 +173,6 @@ export type {
173
173
  StoppedReason,
174
174
  PrepareResult,
175
175
  Directive,
176
- PreDirective,
177
176
  BranchEntry,
178
177
  BranchMap,
179
178
  BranchPredicate,