@falai/agent 4.0.0-alpha.11 → 4.0.0-alpha.13
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/dist/cjs/core/FlowSpec.d.ts +2 -0
- package/dist/cjs/core/FlowSpec.d.ts.map +1 -1
- package/dist/cjs/core/FlowSpec.js +26 -3
- package/dist/cjs/core/FlowSpec.js.map +1 -1
- package/dist/cjs/core/Migrate.d.ts.map +1 -1
- package/dist/cjs/core/Migrate.js +3 -1
- package/dist/cjs/core/Migrate.js.map +1 -1
- package/dist/cjs/core/Runner.d.ts +17 -1
- package/dist/cjs/core/Runner.d.ts.map +1 -1
- package/dist/cjs/core/Runner.js +178 -35
- package/dist/cjs/core/Runner.js.map +1 -1
- package/dist/cjs/core/Speak.d.ts.map +1 -1
- package/dist/cjs/core/Speak.js +3 -1
- package/dist/cjs/core/Speak.js.map +1 -1
- package/dist/cjs/types/flow.d.ts +15 -1
- package/dist/cjs/types/flow.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +3 -1
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/utils/outcomes.d.ts +1 -0
- package/dist/cjs/utils/outcomes.d.ts.map +1 -1
- package/dist/cjs/utils/outcomes.js +1 -0
- package/dist/cjs/utils/outcomes.js.map +1 -1
- package/dist/cjs/utils/schema.d.ts +3 -3
- package/dist/cjs/utils/schema.d.ts.map +1 -1
- package/dist/cjs/utils/schema.js +4 -3
- package/dist/cjs/utils/schema.js.map +1 -1
- package/dist/core/FlowSpec.d.ts +2 -0
- package/dist/core/FlowSpec.d.ts.map +1 -1
- package/dist/core/FlowSpec.js +27 -4
- package/dist/core/FlowSpec.js.map +1 -1
- package/dist/core/Migrate.d.ts.map +1 -1
- package/dist/core/Migrate.js +3 -1
- package/dist/core/Migrate.js.map +1 -1
- package/dist/core/Runner.d.ts +17 -1
- package/dist/core/Runner.d.ts.map +1 -1
- package/dist/core/Runner.js +178 -35
- package/dist/core/Runner.js.map +1 -1
- package/dist/core/Speak.d.ts.map +1 -1
- package/dist/core/Speak.js +3 -1
- package/dist/core/Speak.js.map +1 -1
- package/dist/types/flow.d.ts +15 -1
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/session.d.ts +3 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/utils/outcomes.d.ts +1 -0
- package/dist/utils/outcomes.d.ts.map +1 -1
- package/dist/utils/outcomes.js +1 -0
- package/dist/utils/outcomes.js.map +1 -1
- package/dist/utils/schema.d.ts +3 -3
- package/dist/utils/schema.d.ts.map +1 -1
- package/dist/utils/schema.js +4 -3
- package/dist/utils/schema.js.map +1 -1
- package/docs/concepts/collection.md +40 -5
- package/docs/concepts/pipeline.md +6 -5
- package/docs/concepts/runs-and-waits.md +1 -1
- package/docs/guides/branching.md +3 -1
- package/docs/guides/flow-control.md +3 -1
- package/docs/migration/v3-to-v4.md +1 -1
- package/docs/reference/branches.md +1 -1
- package/docs/reference/fields.md +5 -3
- package/docs/reference/flow-spec.md +6 -3
- package/docs/reference/flow.md +8 -3
- package/docs/reference/outcomes.md +2 -1
- package/docs/reference/session.md +2 -0
- package/docs/reference/step.md +7 -4
- package/docs/rfc/v4-one-flow.md +2 -0
- package/examples/05-branches.ts +1 -1
- package/package.json +1 -1
- package/src/core/FlowSpec.ts +33 -5
- package/src/core/Migrate.ts +2 -1
- package/src/core/Runner.ts +167 -32
- package/src/core/Speak.ts +3 -1
- package/src/types/flow.ts +15 -1
- package/src/types/session.ts +3 -0
- package/src/utils/outcomes.ts +1 -0
- package/src/utils/schema.ts +4 -3
package/dist/utils/schema.js
CHANGED
|
@@ -62,7 +62,7 @@ function coerceScalar(def, raw) {
|
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
64
|
* The JSON schema a provider sees: an allow-list of JSON-schema keys, with
|
|
65
|
-
* `ask`, `extract` and `optional` stripped. Closed (`additionalProperties:
|
|
65
|
+
* `label`, `ask`, `extract` and `optional` stripped. Closed (`additionalProperties:
|
|
66
66
|
* false`); every property required unless a parameter says `optional`.
|
|
67
67
|
*/
|
|
68
68
|
export function toWireSchema(defs, options = {}) {
|
|
@@ -90,8 +90,8 @@ function wireProperty(def, nullable) {
|
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
92
|
* Merge field rows authored per flow into the agent's one field set. Two
|
|
93
|
-
* rows for one slug must agree on type and enum; the first non-empty `
|
|
94
|
-
* and `description` win.
|
|
93
|
+
* rows for one slug must agree on type and enum; the first non-empty `label`,
|
|
94
|
+
* `ask` and `description` win.
|
|
95
95
|
*/
|
|
96
96
|
export function buildSchema(groups) {
|
|
97
97
|
const merged = {};
|
|
@@ -115,6 +115,7 @@ export function buildSchema(groups) {
|
|
|
115
115
|
merged[slug] = {
|
|
116
116
|
...seen,
|
|
117
117
|
enum: seen.enum ?? def.enum,
|
|
118
|
+
label: seen.label ?? def.label,
|
|
118
119
|
ask: seen.ask ?? def.ask,
|
|
119
120
|
description: seen.description ?? def.description,
|
|
120
121
|
extract: seen.extract ?? def.extract,
|
package/dist/utils/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/utils/schema.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/utils/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ order: 4
|
|
|
7
7
|
|
|
8
8
|
# Field collection
|
|
9
9
|
|
|
10
|
-
A field is one piece of data the conversation collects: a name, a company, a budget, a yes or no. You declare each field once, on the agent, with how to ask for it.
|
|
10
|
+
A field is one piece of data the conversation collects: a name, a company, a budget, a yes or no. You declare each field once, on the agent, with how to ask for it. A flow lists the fields it needs, and its steps say which to ask and when. The model asks and extracts. Code decides what is still missing.
|
|
11
11
|
|
|
12
12
|
## Declared once
|
|
13
13
|
|
|
@@ -41,12 +41,45 @@ type Data = DataOf<typeof f>;
|
|
|
41
41
|
|---|---|
|
|
42
42
|
| `type` | `'string'`, `'number'`, `'integer'` or `'boolean'`. Values are coerced to it on the way in. |
|
|
43
43
|
| `enum` | The allowed values. A value outside the list is dropped. It becomes a literal union in `Data`. |
|
|
44
|
+
| `label` | The name a person reads, in an editor or next to a collected value. The model never sees it. |
|
|
44
45
|
| `description` | What the field means, for the model. |
|
|
45
46
|
| `ask` | How the model should ask for it. A step may override it. |
|
|
46
47
|
| `extract` | Where a value may come from: `'anywhere'` or `'asked'`. The default depends on `type`, below. |
|
|
47
48
|
|
|
48
49
|
`f.fields()` binds the data type, so `collect`, `ask`, `clearOnStart`, `{ step, clear }`, `if: { equals }` and an action's `ctx.set()` are all checked against these field names at compile time. The collected values live in `session.data` as a `Partial<Data>`.
|
|
49
50
|
|
|
51
|
+
## A flow's data, a step's questions
|
|
52
|
+
|
|
53
|
+
A scheduling flow needs one set of fields and a triage flow another. The flow's `collect` lists the fields it needs. Each talk step's `collect` says which of them to ask now, in what order: one field, or several when the step's prompt asks them together.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { falai } from "@falai/agent";
|
|
57
|
+
|
|
58
|
+
const f = falai().fields({
|
|
59
|
+
nome: { type: "string", label: "Nome", ask: "Pergunte o nome." },
|
|
60
|
+
dia: { type: "string", label: "Dia", ask: "Pergunte qual dia fica melhor." },
|
|
61
|
+
orcamento: { type: "number", label: "Orçamento" },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const agenda = f.flow({
|
|
65
|
+
id: "agenda",
|
|
66
|
+
name: "Agendamento",
|
|
67
|
+
on: [{ message: ["quer agendar uma visita"] }],
|
|
68
|
+
collect: ["nome", "dia", "orcamento"],
|
|
69
|
+
steps: [
|
|
70
|
+
{ id: "quem", collect: ["nome"], question: "Claro! Qual é o seu nome?" },
|
|
71
|
+
{ id: "quando", prompt: "Ofereça terça ou quinta.", collect: ["dia"] },
|
|
72
|
+
{ id: "fim", say: "Combinado, {{data.nome}}. Até {{data.dia}}." },
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export { agenda };
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
No step asks for `orcamento`. It is still on the flow's list, so when the customer mentions a budget while this flow holds the conversation, the value is noted. A field on the list that only an answer can fill (`extract: 'asked'`, every boolean by default) and that no step asks can never be filled; `validateFlow` warns about it.
|
|
80
|
+
|
|
81
|
+
Step one asks with fixed text: `question`. Its first ask goes out word for word, with no model call. It goes out only when every field the step collects is still missing. If the customer's first message already gave the name, the step is skipped. A later ask is the model's own wording, so a customer who replies with a question gets an answer.
|
|
82
|
+
|
|
50
83
|
## Known and pending
|
|
51
84
|
|
|
52
85
|
A field is **known** when its value is not `undefined`, `null` or `''`. Anything else is unknown. There is no "asked but refused" state, only a count.
|
|
@@ -69,7 +102,7 @@ Four writers reach `session.data`. Two are the model, two are your code.
|
|
|
69
102
|
|
|
70
103
|
| Writer | Which fields | When |
|
|
71
104
|
|---|---|---|
|
|
72
|
-
| The understand call | Unknown fields with `extract: 'anywhere'`
|
|
105
|
+
| The understand call | Unknown fields with `extract: 'anywhere'` that the floor's flow or a candidate `message` flow lists, in its own `collect` or a talk step's. With nobody on the floor, also the catch-all's (`message: []`) | On a message, before runs move |
|
|
73
106
|
| The speak call | The speaking step's pending fields, whatever their `extract`, in the envelope `{ message, ...fields }` | When the step speaks |
|
|
74
107
|
| A tool's `data` | Whatever the tool returns | During a speak round, written as given |
|
|
75
108
|
| An action's `ctx.set(patch)` | Whatever the action writes | During a `do` step, written as given |
|
|
@@ -110,7 +143,7 @@ This split also decides which call spends tokens on what. On a message, the unde
|
|
|
110
143
|
{ kind: 'collect', status: 'skipped', code: 'max-asks', detail: 'orcamento' }
|
|
111
144
|
```
|
|
112
145
|
|
|
113
|
-
One line per field that ran out of asks, with the field's slug in `detail`. `maxAsks: 1` means "ask once, do not insist". The field stays unknown: a later step may still collect it, and a later run of the flow starts the count again, because `asked` lives on the run.
|
|
146
|
+
One line per field that ran out of asks, with the field's slug in `detail`. `maxAsks: 1` means "ask once, do not insist". The field stays unknown: a later step may still collect it, and a later run of the flow starts the count again, because `asked` lives on the run. A step's fixed `question` counts as one ask. `{ step, clear }` resets the count of the fields it clears, so a confirmation loop asks from scratch each round.
|
|
114
147
|
|
|
115
148
|
## Known fields are never re-extracted
|
|
116
149
|
|
|
@@ -157,11 +190,13 @@ Three layers of text shape the question, from general to specific.
|
|
|
157
190
|
2. The step's `ask: { orcamento: '…' }`: wins over the field's, for that step only.
|
|
158
191
|
3. The step's `prompt`: the guideline for the whole reply. Without one, the default is "Collect what is still missing below, in the flow of the conversation, one or two things per message."
|
|
159
192
|
|
|
160
|
-
|
|
193
|
+
A step's `question` skips all three for the first ask: it is the exact text the customer reads. Every later ask goes back to the three layers.
|
|
194
|
+
|
|
195
|
+
All four are templates: `{{data.x}}`, `{{context.x}}` and `{{input.x}}` are filled in before the model reads them. The model sees every pending field of the step with its wording, in collect order, and is told to ask at the pace the prompt sets and to take any value the customer's message already answers.
|
|
161
196
|
|
|
162
197
|
## What the provider sees
|
|
163
198
|
|
|
164
|
-
`toWireSchema` in `src/utils/schema.ts` is the only way a field definition reaches a provider. It keeps `type`, `description` and `enum` and strips `ask`, `extract` and `optional`: those are for the framework, not the model. The result is a closed JSON schema (`additionalProperties: false`). In both envelopes every property is required and nullable, so the model must answer each field with a value or `null`. A field name that is not a legal property name for the provider, or is spelled `message`, travels under an alias and is mapped back on the way out.
|
|
199
|
+
`toWireSchema` in `src/utils/schema.ts` is the only way a field definition reaches a provider. It keeps `type`, `description` and `enum` and strips `label`, `ask`, `extract` and `optional`: those are for the framework, not the model. The result is a closed JSON schema (`additionalProperties: false`). In both envelopes every property is required and nullable, so the model must answer each field with a value or `null`. A field name that is not a legal property name for the provider, or is spelled `message`, travels under an alias and is mapped back on the way out.
|
|
165
200
|
|
|
166
201
|
## Where next
|
|
167
202
|
|
|
@@ -62,7 +62,7 @@ Code first works out what there is to judge:
|
|
|
62
62
|
- **Candidates**: the flow holding the floor, whatever its trigger, plus every `message` flow with a non-empty phrase list whose `if` holds and whose `repeat` allows a start, in flow order. `message: []` catch-alls are never scored.
|
|
63
63
|
- **Mentions**: every `mention` flow with a non-empty list whose `repeat` allows a start.
|
|
64
64
|
- **Branches**: the `when` branches of the asking step.
|
|
65
|
-
- **Fields**: every unknown field with `extract: 'anywhere'`
|
|
65
|
+
- **Fields**: every unknown field with `extract: 'anywhere'` that the floor's flow or a candidate flow lists, in its own `collect` or a talk step's. With nobody on the floor, the catch-all that would take the message adds its fields too, because an opening message often says the most. The fields its first step asks are read by that step's own speak call, so on their own they are not worth a call. A first step with a fixed `question` has no speak call, so its fields do count.
|
|
66
66
|
|
|
67
67
|
Then the shortcuts, each worth zero calls:
|
|
68
68
|
|
|
@@ -84,13 +84,13 @@ Code applies the judgement in a fixed order.
|
|
|
84
84
|
|
|
85
85
|
## 5. Run
|
|
86
86
|
|
|
87
|
-
If no run is asking, the most recently suspended one returns to asking. Then every live run is moved in turn; a child started by `then: { flow }` joins the queue. A run moves only if it can: `waiting` and `suspended` runs stay put, and an asking run re-speaks only on a message, never on a wake or an event.
|
|
87
|
+
If no run is asking, the most recently suspended one returns to asking. Then every live run is moved in turn; a child started by `then: { flow }` joins the queue. On a message that nothing has answered once the queue is empty, with nobody asking and no `silenced`, the most recently suspended run returns to asking and moves too, and so on down the stack until one answers: an asker that moved on without a word hands the message back to the run it suspended. A run that returns to asking on a message has its step's `if` branches judged first, as Decide does for the asker. A run moves only if it can: `waiting` and `suspended` runs stay put, and an asking run re-speaks only on a message, never on a wake or an event.
|
|
88
88
|
|
|
89
89
|
Before a run moves, its premise is re-checked with this turn's context: `while` when the flow has one, otherwise the trigger's `if`. A false premise ends the run: `code: 'premise-changed'`. A silence-started run moved by a wake also ends when the customer has written since it started: `code: 'customer-replied'`. A flow the agent no longer has ends the run with `code: 'flow-gone'`; a missing step, `code: 'step-gone'`.
|
|
90
90
|
|
|
91
91
|
Then the run walks its steps until one stops it.
|
|
92
92
|
|
|
93
|
-
- **Talk** (`prompt` / `collect`). Pending is `collect` minus known minus at `maxAsks`. A collect step with nothing pending is skipped with no call (`code: 'already-known'`) and the run continues. Under `silenced`, a resuming asker stays asking and any other run ends `code: 'silenced'` (`detail` = your reason). Otherwise every other asking run is suspended, this run becomes `asking`, and it is the turn's speaker, unless speaking already happened this turn, in which case it waits for the next message.
|
|
93
|
+
- **Talk** (`prompt` / `collect`). Pending is `collect` minus known minus at `maxAsks`. A collect step with nothing pending is skipped with no call (`code: 'already-known'`) and the run continues, except on the step an `onEnd: 'stay'` run stays on: that one answers anyway. Under `silenced`, a resuming asker stays asking and any other run ends `code: 'silenced'` (`detail` = your reason). Otherwise every other asking run is suspended, this run becomes `asking`, and it is the turn's speaker, unless speaking already happened this turn, in which case it waits for the next message.
|
|
94
94
|
- **Say.** The text goes to `messages[]` as `kind: 'verbatim'` with the pending `afterMs`. `once` writes a claim; a repeat is `code: 'already-sent'`. Under `silenced` the run ends `code: 'silenced'` (`detail` = your reason).
|
|
95
95
|
- **Do.** The action runs now, with `with` rendered against `data`, `context` and `input`, under `key = ${runId}:${stepId}:${visit}`. `{ ok }` continues (`spoke: true` makes this run the one that answered); `{ skipped }` continues (`code: 'action-skipped'`); `{ failed }` takes `onFail` or continues (`code: 'action-failed'`); `{ defer }` parks the run under a new wake and re-runs the same step, same key, when it fires. An unknown action is `code: 'action-failed'` with `detail: 'unknown action "notify"'`; a thrown error is `code: 'action-failed'`, the error message in `detail`.
|
|
96
96
|
- **Wait** (timer). Ten seconds or less, when the next step is a `say` or a talk step: the delay rides on that message as `afterMs` (`code: 'inline-delay'`, `detail: '3000ms'`). Anything else parks the run and adds `{ key, at }` to `schedule[]`, with `at` moved forward to the next business hour when the step sets `businessHours: true`.
|
|
@@ -123,7 +123,7 @@ The envelope is `{ message, ...pending fields of this step }`, every property re
|
|
|
123
123
|
|
|
124
124
|
The one place the spoken result is applied.
|
|
125
125
|
|
|
126
|
-
**Spoken.** The message goes to `messages[]` as `kind: 'ai'` with `key = ${runId}:${stepId}:${visit}`, or `idle:<trigger key>` for the idle speaker. Envelope values are validated and written like phase 4; tool `data` patches are written as given. Pending is recomputed. Each field still pending gets `asked + 1` and the run stays `asking`. Nothing pending: a field that hit `maxAsks` is reported (`code: 'max-asks'`, one line per field), the run takes `then` and keeps moving this turn, except that a talk step reached now waits for the next message.
|
|
126
|
+
**Spoken.** The message goes to `messages[]` as `kind: 'ai'` with `key = ${runId}:${stepId}:${visit}`, or `idle:<trigger key>` for the idle speaker. Envelope values are validated and written like phase 4; tool `data` patches are written as given. Pending is recomputed. Each field still pending gets `asked + 1` and the run stays `asking`. Nothing pending: a field that hit `maxAsks` is reported (`code: 'max-asks'`, one line per field), the run takes `then` and keeps moving this turn, except that a talk step reached now waits for the next message. A run staying on its step (`onEnd: 'stay'`) takes no `then`: it stays there for the next message, one visit later. A run that reaches the end of an `onEnd: 'stay'` flow goes back to the last talk step it took; when another run is asking, it waits `suspended` behind it, unless this message was routed to it.
|
|
127
127
|
|
|
128
128
|
**Deferred.** A failure a wait can fix — the provider was down, slow or rate-limited — re-parks the talk step under `${runId}:${stepId}:${visit}:retry:${atMs}`: +1m, +5m, +15m, +1h, +6h, then the run ends `failed`. A failure it cannot fix — a rejected key, a prompt past the context window — ends the run at once. The session is saved with everything phase 5 did. The retry wake re-runs the step under the same key, so the `do` steps before it do not run again.
|
|
129
129
|
|
|
@@ -140,11 +140,12 @@ Every row but the last is asserted by a scenario in `tests/scenarios/`; the comp
|
|
|
140
140
|
| Turn | Calls | Why | Scenario |
|
|
141
141
|
|---|---|---|---|
|
|
142
142
|
| A message with a floor holder, several candidate flows, or one candidate that a catch-all or the idle speaker could stand in for | 2 | understand, then speak | S1, S13 |
|
|
143
|
-
| A message with nothing to judge: a catch-all alone, a floor holder alone, or one flow with `idle: 'silent'` and no catch-all | 1 | speak only | S0, S1, S8 |
|
|
143
|
+
| A message with nothing to judge: a catch-all alone with nothing to learn beyond what its first step asks, a floor holder alone, or one flow with `idle: 'silent'` and no catch-all | 1 | speak only | S0, S1, S8, S14 |
|
|
144
144
|
| A message with no flows, answered by the idle speaker | 1 | speak only | S9 |
|
|
145
145
|
| A message where a mention flow's `say` answers | 1 | understand only; the floor's talk is skipped | S4 |
|
|
146
146
|
| Each tool round | +1 | one more speak call | S8, S9 |
|
|
147
147
|
| A wake or start that reaches a talk step | 1 | speak only; there is no message to understand | S2, S5, S12 |
|
|
148
|
+
| A step's first ask with a fixed `question` | 0 | the text goes out as written | S14 |
|
|
148
149
|
| A wake or start that runs only `do`, `wait` and `if` steps | 0 | code only | S5 (the start; its defer test covers the wake) |
|
|
149
150
|
| Any input under `silenced` (a plain reason) | 0 | `do` steps run, nobody speaks; `{ reason, understand: true }` still spends the understand call | S2, S12 |
|
|
150
151
|
| A message with `idle: 'silent'` and no eligible flow | 0 | nothing to judge, nobody speaks | S9 |
|
|
@@ -68,7 +68,7 @@ Every start, whatever the trigger, goes through the same checks in `Runner.start
|
|
|
68
68
|
At most one run in a session is `asking`. That run holds the floor: its talk step spoke last, and the next message is read as its answer.
|
|
69
69
|
|
|
70
70
|
- A talk step reached by any other run suspends the asker (`status: 'suspended'`, `suspendedAt: now`) and takes the floor. A follow-up nudge that fires while triage is mid-question does exactly this.
|
|
71
|
-
- Whenever nobody is asking, at the start of phase 5 and again after Speak, the **most recently suspended** run returns to asking. It is a stack: `suspendedAt` decides, not `startedAt`. `tests/runner.test.ts` ("the floor") pins this with three runs.
|
|
71
|
+
- Whenever nobody is asking, at the start of phase 5 and again after Speak, the **most recently suspended** run returns to asking. On a message, it also happens in phase 5 once every run has moved, when nothing has answered yet; the resumed run then answers it, or the next one down the stack does if it too moves on without a word. It is a stack: `suspendedAt` decides, not `startedAt`. `tests/runner.test.ts` ("the floor") pins this with three runs.
|
|
72
72
|
- An asking run re-speaks only on a message. Wakes and events leave it alone.
|
|
73
73
|
- On a message, routing may move the floor to another flow: it needs a score of at least 40 and at least 15 above the asker's. Then that flow's suspended run resumes, or a new run starts. Routing never takes the floor from a run that took it in Ingest (a resolved wait, a wake). [Triggers](../guides/triggers.md) has the routing rules.
|
|
74
74
|
- One answer per message. When a run other than the floor holder answered this turn (a `say`, or a `do` returning `spoke: true`), the floor's talk is skipped (`code: 'another-reply'`) and the asker stays asking.
|
package/docs/guides/branching.md
CHANGED
|
@@ -64,7 +64,7 @@ console.log(t2.messages.map((m) => m.text)); // ["Claro, vou chamar alguém da e
|
|
|
64
64
|
|
|
65
65
|
`branches` is allowed on two step kinds:
|
|
66
66
|
|
|
67
|
-
- A talk step that collects (`collect`, with or without a `prompt`). Both `when` and `if` branches work while it asks. A `prompt` step with no `collect` speaks once and moves on in the same turn, so its branches are only judged in the rare turn where it is still asking: the
|
|
67
|
+
- A talk step that collects (`collect`, with or without a `prompt`). Both `when` and `if` branches work while it asks. A `prompt` step with no `collect` speaks once and moves on in the same turn, so its branches are only judged in the rare turn where it is still asking: the talk step an `onEnd: 'stay'` flow stays on, or a turn where another run answered the customer first. On the step a run stays on, an `if` branch that leads to `'end'` or to a step the run has already been through is not taken: that path already ran, and the fact would still hold on every message. Write a restart there as a `when`.
|
|
68
68
|
- A timer `wait` step (`wait: '2d'`). Only `if` branches are judged there.
|
|
69
69
|
|
|
70
70
|
`say`, `do`, `if` and `wait: { event }` steps have no branches. A code fork between them is an `if` step.
|
|
@@ -79,6 +79,8 @@ console.log(t2.messages.map((m) => m.text)); // ["Claro, vou chamar alguém da e
|
|
|
79
79
|
|
|
80
80
|
If no branch holds, the step carries on: it speaks again with what is still pending, or completes when its fields are known.
|
|
81
81
|
|
|
82
|
+
A suspended run that gets the conversation back in the middle of a turn, because the run that was asking finished without a word, is checked the same way before it speaks, but only its `if` branches: the model was not asked about its `when` branches this turn.
|
|
83
|
+
|
|
82
84
|
**On a `wait` step**, the branches are judged only when the customer replies while the run is parked, which is also when `else` applies. The first `if` branch that holds wins over `else`. A `wait` with no `else` ignores the reply, branches included. When the timer fires, branches are not consulted: the run takes `then`, or `else` if the customer wrote after the wait was set. The outcome line reads `code: 'replied'` or `code: 'no-reply'`.
|
|
83
85
|
|
|
84
86
|
```ts
|
|
@@ -151,7 +151,7 @@ Ends this run with `reason: 'flow'` and starts the other flow in the same turn.
|
|
|
151
151
|
| `onEnd` | What happens | `ended[].reason` |
|
|
152
152
|
|---|---|---|
|
|
153
153
|
| `'end'` (default) | the run ends; the session is idle | `'end'` |
|
|
154
|
-
| `'stay'` | the run
|
|
154
|
+
| `'stay'` | the run goes back to the last talk step it took and answers every later message from there, with a new key each time; the steps after that talk step do not run again | none: the run does not end |
|
|
155
155
|
| `'reset'` | the run ends and a fresh run of the same flow starts at the first step, data kept, one hop deeper | `'reset'` |
|
|
156
156
|
|
|
157
157
|
```ts
|
|
@@ -169,6 +169,8 @@ const faq = f.flow({
|
|
|
169
169
|
});
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
+
The talk step does not have to be the last step, and it does not need anything left to collect. A flow that asks for the name, tells the team, and then keeps talking is `steps: [{ id: "quem", collect: ["nome"] }, { id: "avisa", do: "notify" }]` with `onEnd: "stay"`: `avisa` runs once, then `quem` answers every message, even though the name is known. A `say` or a `do` that returned `spoke: true` on the way to the end counts as the answer to that message, so the step waits for the next one. A flow with no talk step ends, as with `'end'`.
|
|
173
|
+
|
|
172
174
|
`'reset'` is a chain into the same flow, so it costs a hop: a flow with no talk step that resets forever stops at the hop cap instead of spinning.
|
|
173
175
|
|
|
174
176
|
## `while`: the run's premise
|
|
@@ -147,7 +147,7 @@ const triagem = f.flow({
|
|
|
147
147
|
{ id: 'aviso', do: 'notify', with: { recipient: 'owner', message: 'Lead: {{data.nome}} ({{data.empresa}})' } },
|
|
148
148
|
{ id: 'tchau', say: 'Um vendedor continua daqui.' },
|
|
149
149
|
],
|
|
150
|
-
onEnd: 'end', // or 'stay' (
|
|
150
|
+
onEnd: 'end', // or 'stay' (the last talk step answers every later message) or 'reset' (first step, data kept)
|
|
151
151
|
});
|
|
152
152
|
```
|
|
153
153
|
|
|
@@ -34,7 +34,7 @@ type Branch<C = unknown, D = unknown> = { then: Next<D> } & (
|
|
|
34
34
|
3. The run leaves the step with the outcome `code: 'branch'` (kind `prompt` or `collect`, status `ok`, `next` naming the target) and follows `then` in the same turn. Fields the understand call extracted from the same message are written first, so `then` may land on a step that is already satisfied.
|
|
35
35
|
4. When no branch holds, the step continues as usual: pending fields are harvested and the step speaks again.
|
|
36
36
|
|
|
37
|
-
A `when` branch costs the understand call; when it is the only thing to judge, that is one model call the turn would not otherwise spend. An `if` branch is judged on every message turn even when no understand call happens.
|
|
37
|
+
A `when` branch costs the understand call; when it is the only thing to judge, that is one model call the turn would not otherwise spend. An `if` branch is judged on every message turn even when no understand call happens. A suspended run that returns to asking during the turn, because the asker ended or moved on without a word, has its `if` branches judged before its step speaks; its `when` branches were not in the understand call, so they wait for the next message.
|
|
38
38
|
|
|
39
39
|
**On a wait step.** Only `if` branches are judged, and only when the customer writes before the time passes and the step has `else`. The first `if` branch that holds replaces `else` as the target (outcome `code: 'replied'`). A `when` branch on a wait step is never asked, because a reply to a wait step does not reach the understand call. When the wake fires, branches are not consulted: the run follows `then`, or `else` when the customer wrote after the wait was set.
|
|
40
40
|
|
package/docs/reference/fields.md
CHANGED
|
@@ -21,6 +21,7 @@ interface ScalarDef<T extends ScalarType = ScalarType> {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
interface FieldDef<T extends ScalarType = ScalarType> extends ScalarDef<T> {
|
|
24
|
+
label?: string;
|
|
24
25
|
ask?: string;
|
|
25
26
|
extract?: "anywhere" | "asked";
|
|
26
27
|
}
|
|
@@ -37,6 +38,7 @@ type DataOf<T extends { fields: FieldDefs }> = InferData<T["fields"]>;
|
|
|
37
38
|
|---|---|---|---|
|
|
38
39
|
| `type` | `ScalarType` | required | `'string'`, `'number'`, `'integer'` or `'boolean'`. |
|
|
39
40
|
| `enum` | `readonly (string \| number)[]` | none | The allowed values. A value outside the list is dropped (`code: 'not-in-enum'`). In the data type the field becomes the literal union. |
|
|
41
|
+
| `label` | `string` | none | The name a person reads, in an editor or next to a collected value. Never sent to the model. |
|
|
40
42
|
| `description` | `string` | none | What the field is. Sent to the model with the field's type and options. |
|
|
41
43
|
| `ask` | `string` | none | How the model should ask for it. Sent to the speak call as "How to ask" while the field is pending. A talk step's own `ask` overrides it. A template: `{{data.x}}` and `{{context.x}}` are filled in. |
|
|
42
44
|
| `extract` | `'anywhere' \| 'asked'` | `'anywhere'` for string, number and integer; `'asked'` for boolean | Where a value may be taken from. `'anywhere'`: any message from the customer, whether or not the field was asked. `'asked'`: only the reply to the step that lists the field, so a stray "sim" never confirms anything. |
|
|
@@ -55,7 +57,7 @@ Properties are readonly — `fields()` takes the definitions as a `const` type,
|
|
|
55
57
|
|
|
56
58
|
**Known.** A field is known when its value is not `undefined`, `null` or `''`. Known fields are never asked again and never re-extracted; both calls list them under "Already known".
|
|
57
59
|
|
|
58
|
-
**Which call extracts what.** The understand call, on a message turn, extracts every unknown `'anywhere'` field
|
|
60
|
+
**Which call extracts what.** The understand call, on a message turn, extracts every unknown `'anywhere'` field that the flow holding the floor or an eligible message flow lists, in the flow's own `collect` or a talk step's, in one envelope. With nobody on the floor, the catch-all's fields are read too. The speak call extracts the pending fields of the step that speaks, `'asked'` ones included, in the same call that phrases the reply. Tools write `data` and actions call `ctx.set()`; those values are written as given, with no check.
|
|
59
61
|
|
|
60
62
|
**Coercion.** A raw value from the model goes through `coerceField(def, raw)` before it is written:
|
|
61
63
|
|
|
@@ -68,9 +70,9 @@ Properties are readonly — `fields()` takes the definitions as a `const` type,
|
|
|
68
70
|
|
|
69
71
|
Anything else is dropped with the outcome line `code: 'bad-value'`. A value outside `enum` is dropped with `code: 'not-in-enum'`. A value for a slug that is not a field is dropped with `code: 'unknown-field'`. Dropped values leave a `collect` outcome with status `skipped` and no run id; the field stays pending and is asked again.
|
|
70
72
|
|
|
71
|
-
**What reaches the provider.** `toWireSchema(defs)` turns definitions into the JSON schema the model must fill: a closed object (`additionalProperties: false`) with one property per field carrying `type`, `description` and `enum`, and nothing else. `ask` and `extract` are stripped; they
|
|
73
|
+
**What reaches the provider.** `toWireSchema(defs)` turns definitions into the JSON schema the model must fill: a closed object (`additionalProperties: false`) with one property per field carrying `type`, `description` and `enum`, and nothing else. `label`, `ask` and `extract` are stripped; they are for people and the framework, not the schema. In the envelope style used by both calls every property is required and nullable (`type: [type, 'null']`), so the model answers `null` for what the customer did not give. The prompt describes each field once more in words (`nome (string) [a | b]: description`) and, in the speak call, adds the pending field's "How to ask".
|
|
72
74
|
|
|
73
|
-
**Clearing.** `{ step, clear: ['x'] }` on a `then`/`else`/branch and `clearOnStart` on a flow delete the field from `session.data`, so the next step that collects it asks again. `maxAsks` on a talk step (default 3) is the other way a field stops being pending: it is skipped for that run with `code: 'max-asks'`, the field in `detail`.
|
|
75
|
+
**Clearing.** `{ step, clear: ['x'] }` on a `then`/`else`/branch and `clearOnStart` on a flow delete the field from `session.data`, so the next step that collects it asks again. `{ step, clear }` also resets how many times the run asked it. `maxAsks` on a talk step (default 3) is the other way a field stops being pending: it is skipped for that run with `code: 'max-asks'`, the field in `detail`.
|
|
74
76
|
|
|
75
77
|
**Action parameters** use the sibling type `ParamDef`: a `ScalarDef` plus `optional?: true`, or `{ type: 'array', items: ScalarDef }`. `InferParams<P>` gives the `with` shape. They share `toWireSchema` and the same strictness at construction. See [Actions, events and conditions](actions-events-conditions.md).
|
|
76
78
|
|
|
@@ -23,6 +23,7 @@ interface FlowSpec {
|
|
|
23
23
|
on?: TriggerSpec[];
|
|
24
24
|
anchor?: string;
|
|
25
25
|
while?: ConditionSpec;
|
|
26
|
+
collect?: string[];
|
|
26
27
|
clearOnStart?: string[];
|
|
27
28
|
steps: StepSpec[];
|
|
28
29
|
onEnd?: "end" | "stay" | "reset";
|
|
@@ -33,7 +34,7 @@ interface FlowSpec {
|
|
|
33
34
|
type StepSpec = StepBase &
|
|
34
35
|
(
|
|
35
36
|
| { kind: "prompt"; prompt: Template; ask?; maxAsks?; branches?: BranchSpec[]; tools?; instructions?: InstructionSpec[] }
|
|
36
|
-
| { kind: "collect"; collect: string[]; prompt?: Template; ask?; maxAsks?; branches?: BranchSpec[]; tools?; instructions?: InstructionSpec[] }
|
|
37
|
+
| { kind: "collect"; collect: string[]; prompt?: Template; question?: Template; ask?; maxAsks?; branches?: BranchSpec[]; tools?; instructions?: InstructionSpec[] }
|
|
37
38
|
| { kind: "say"; say: Template; media?: { slug: string }; once?: boolean }
|
|
38
39
|
| { kind: "do"; do: string; with?: Record<string, unknown>; onFail?: Next }
|
|
39
40
|
| { kind: "wait"; wait: Duration; businessHours?: boolean; else?: Next; branches?: BranchSpec[] }
|
|
@@ -120,7 +121,7 @@ Every message has the form `[FlowConfigurationError] <where>: <what>. <fix>`, wh
|
|
|
120
121
|
| Reserved step id | `uses the reserved id "end"` | "end" ends the run; pick another id. |
|
|
121
122
|
| Duplicate step id | `duplicates an earlier step id` | Give each step its own id. |
|
|
122
123
|
| Triggers, no steps | `has triggers but no steps` | Add at least one step or remove `on`. |
|
|
123
|
-
| Unknown field | `unknown field "x" in collect` (also `ask`, `clearOnStart`, `then.clear`, `while.equals`, `if.known`, …) | Add it to the agent's fields or fix the slug. |
|
|
124
|
+
| Unknown field | `unknown field "x" in collect` (the flow's or a step's; also `ask`, `clearOnStart`, `then.clear`, `while.equals`, `if.known`, …) | Add it to the agent's fields or fix the slug. |
|
|
124
125
|
| Unknown tool | `unknown tool "x"` (flow or step `tools`) | Register it in the agent's tools or fix the name. |
|
|
125
126
|
| Unknown action | `unknown action "x"` | Register it in actions or fix the name. |
|
|
126
127
|
| Unknown event | `unknown event "x"` (trigger) or `unknown event "x" in wait` | Register it in events or fix the name. |
|
|
@@ -136,6 +137,7 @@ Every message has the form `[FlowConfigurationError] <where>: <what>. <fix>`, wh
|
|
|
136
137
|
| Extra parameter | `action "notify" has no parameter "to"` | Remove it or fix the name. |
|
|
137
138
|
| Branch without a test | `branches[0] has neither when nor if` | Give the branch an AI condition (when) or a code one (if). |
|
|
138
139
|
| Backward `if` with no `else` | `"if" jumps back to "quem" with no else` | Add else so the false branch has somewhere to go. |
|
|
140
|
+
| Fixed question, nothing to ask | `has a question but collects nothing` | A fixed question asks for fields: add collect, or send the text with a say step. |
|
|
139
141
|
|
|
140
142
|
Parameter values are checked strictly: `"3"` is not a number, `3.5` is not an integer, and an `enum` must contain the value unless the string holds `{{`, because a template's value is only known at run time.
|
|
141
143
|
|
|
@@ -146,7 +148,8 @@ Two checks live in the agent constructor rather than in `validateFlow`: `flow "x
|
|
|
146
148
|
| Warning | Why |
|
|
147
149
|
|---|---|
|
|
148
150
|
| `flow "f", step "s": then jumps back to "quem" without clear; the fields collected since stay known and those steps skip. Add clear: [...] to re-ask them.` | A `then`, `else`, `onFail` or branch target points at the same or an earlier step and clears nothing, so a collect step it lands on is skipped with `code: 'already-known'`. |
|
|
149
|
-
| `flow "f", step "s": collects "nome", "empresa" with no prompt and no ask; the model has nothing to go on. Add a prompt or an ask per field.` | A collect step with no `prompt`, where no listed field has an `ask` on the step or on the agent. |
|
|
151
|
+
| `flow "f", step "s": collects "nome", "empresa" with no prompt and no ask; the model has nothing to go on. Add a prompt or an ask per field.` | A collect step with no `prompt` and no `question`, where no listed field has an `ask` on the step or on the agent. |
|
|
152
|
+
| `flow "f": collect lists "confirmado", which is only taken from the answer to a step that asks it, and no step does. Add it to a step's collect, or set extract: 'anywhere' on the field.` | A field in the flow's `collect` with `extract: 'asked'` (every boolean, by default) that no step's `collect` lists, so nothing can ever fill it. |
|
|
150
153
|
|
|
151
154
|
## flowSpecSchema
|
|
152
155
|
|
package/docs/reference/flow.md
CHANGED
|
@@ -19,6 +19,7 @@ interface Flow<C = unknown, D = unknown> {
|
|
|
19
19
|
on?: Trigger<C, D>[];
|
|
20
20
|
anchor?: string;
|
|
21
21
|
while?: Pred<C, D>;
|
|
22
|
+
collect?: (keyof D & string)[];
|
|
22
23
|
clearOnStart?: (keyof D & string)[];
|
|
23
24
|
steps: Step<C, D>[];
|
|
24
25
|
onEnd?: "end" | "stay" | "reset";
|
|
@@ -37,6 +38,7 @@ interface Flow<C = unknown, D = unknown> {
|
|
|
37
38
|
| `on` | `Trigger<C, D>[]` | none | What starts a run. Absent or empty: only `turn({ start })` or another flow's `then: { flow }` starts it. See [Trigger](trigger.md). |
|
|
38
39
|
| `anchor` | `string` | `'session'` | What a run is keyed to. `'session'` uses the session id. Any other name reads `input.anchors[name].key`, and falls back to the session id when the host did not pass that anchor. |
|
|
39
40
|
| `while` | `Pred<C, D>` | the trigger's `if` | Re-checked before the run moves. When it stops holding, the run ends with `code: 'premise-changed'`. |
|
|
41
|
+
| `collect` | `(keyof D & string)[]` | none | The data this flow needs, as agent field slugs. Talk steps' `collect` says which of it to ask, and in what order. |
|
|
40
42
|
| `clearOnStart` | `(keyof D & string)[]` | none | Fields forgotten when a run of this flow starts, so a second run asks for them again. |
|
|
41
43
|
| `steps` | `Step<C, D>[]` | required | In order. A run enters `steps[0]` and moves to the next step unless `then` says otherwise. See [Step](step.md). |
|
|
42
44
|
| `onEnd` | `'end' \| 'stay' \| 'reset'` | `'end'` | What the run does after its last step. |
|
|
@@ -59,11 +61,13 @@ It enters `steps[0]` in the same turn unless the trigger has `after`. Keys and s
|
|
|
59
61
|
|
|
60
62
|
**`while`.** Checked every time the run is about to move: at the start of each turn's run phase for a running run, and when an asking run resumes on a message. A parked run (`waiting`) or a suspended one is not checked until it moves again. Without `while`, the check is the trigger's `if`: the run holds while any trigger of the same kind as the one that started it would still fire. A run started by `start` or by another flow has no such trigger, so without `while` it always holds. A silence run also ends, with `code: 'customer-replied'`, when a wake finds that the customer wrote after the run started.
|
|
61
63
|
|
|
64
|
+
**`collect`.** The flow's data is its `collect` plus every field its talk steps collect. While the flow holds the conversation, or could take it on this message, the understand call notes any of it the customer gives that is still unknown and has `extract: 'anywhere'`. A field no step asks is still noted that way; it is just never asked for. When nobody holds the conversation, the catch-all (`message: []`) that would take the message counts as a flow that could take it. See [Field collection](../concepts/collection.md).
|
|
65
|
+
|
|
62
66
|
**`clearOnStart`.** Applied at start for every trigger kind, `start` and `{ flow }` chains included. Not applied when `onEnd: 'reset'` restarts the flow: reset keeps the data.
|
|
63
67
|
|
|
64
68
|
**`onEnd`.**
|
|
65
69
|
- `'end'`: the run ends with reason `'end'`.
|
|
66
|
-
- `'stay'`: the run
|
|
70
|
+
- `'stay'`: the run goes back to the last talk step it took and stays there, asking. On a branched flow that is the step on its own path; when its log names none, the flow's last talk step. From then on that step answers every message, even when it has nothing left to collect, and each answer is a new visit, so a new key. The steps after it ran once, on the way to the end, and do not run again. If the run finishes on a message nothing has answered yet (no `say`, no `spoke: true`), the step answers it in that same turn. If another run is asking, the staying run waits `suspended` behind it and takes over when that run is done, answering the message that run moved on from without a word. When the customer's message belongs to this run (it was routed to this flow, or it resolved this run's `wait`), the run takes the conversation instead: the other asker is suspended, and this run answers unless its own `say` already did. Its `when` branches are judged on every message and still move the run. Its `if` branches are judged too, except one that leads to `'end'` or to a step the run has already been through: that path already ran and the fact is still true, so it would run again on every message. A field still pending there (a `when` branch took the run to the end before the customer gave it) is asked for on each answer, each with its own key, and `max-asks` is reported once, on the answer that reaches the limit. If the flow is edited off `'stay'` while a run stays, the new `onEnd` applies on that run's next message. A flow with no talk step ends, as with `'end'`.
|
|
67
71
|
- `'reset'`: the run ends with reason `'reset'` and a fresh run of the same flow starts at `steps[0]`, data kept, one hop deeper. A flow that resets forever without asking anything stops at hop 5, with `code: 'hop-limit'`.
|
|
68
72
|
|
|
69
73
|
**Instructions and tools while speaking.** The speak call sees the agent's instructions, then this flow's, then the step's, each already filtered by its `if`. Tools are the step's `tools`; without one, the flow's `tools`; without that, every agent tool.
|
|
@@ -77,7 +81,8 @@ It enters `steps[0]` in the same turn unless the trigger has `after`. Keys and s
|
|
|
77
81
|
- no `id`, or `steps` is not a list
|
|
78
82
|
- a step with no `id`, the id `'end'`, or an id used twice
|
|
79
83
|
- triggers with zero steps
|
|
80
|
-
- an unknown field slug in `clearOnStart`, `collect`, `ask`, `equals`, `known` or a `clear` list
|
|
84
|
+
- an unknown field slug in the flow's `collect`, `clearOnStart`, a step's `collect`, `ask`, `equals`, `known` or a `clear` list
|
|
85
|
+
- a `question` on a talk step that collects nothing
|
|
81
86
|
- an unknown action in `do`; a `with` that misses a required parameter, names one the action does not have, or gives a value of the wrong type (`with` values are not coerced; a `{{template}}` string is accepted for any enum)
|
|
82
87
|
- an unknown event in a trigger or in `wait: { event }`
|
|
83
88
|
- an unknown condition name, or a malformed built-in (`equals` not an object, `known` not a list, `silenced` not a boolean); an `equals` value whose type does not match the field
|
|
@@ -87,7 +92,7 @@ It enters `steps[0]` in the same turn unless the trigger has `after`. Keys and s
|
|
|
87
92
|
- a branch with neither `when` nor `if`
|
|
88
93
|
- an `if` step whose `then` jumps backward with no `else`
|
|
89
94
|
|
|
90
|
-
It returns warnings, logged by the agent, for
|
|
95
|
+
It returns warnings, logged by the agent, for three things that run but probably not as intended: a jump backward without `clear` (the fields collected since stay known, so those steps skip), a `collect` step with no `prompt`, no `question` and no `ask` on any of its fields, and a field in the flow's `collect` that only an answer can fill (`extract: 'asked'`) when no step asks it.
|
|
91
96
|
|
|
92
97
|
`toSpec(flow)` throws `FlowConfigurationError` when a predicate is a function, because a function cannot be stored as JSON.
|
|
93
98
|
|
|
@@ -25,7 +25,7 @@ type StepOutcomeCode =
|
|
|
25
25
|
// A run ended early
|
|
26
26
|
| "step-loop" | "step-gone" | "customer-replied" | "premise-changed" | "silenced"
|
|
27
27
|
// A step
|
|
28
|
-
| "already-known" | "another-reply" | "already-sent" | "branch" | "max-asks"
|
|
28
|
+
| "already-known" | "asked-fixed" | "another-reply" | "already-sent" | "branch" | "max-asks"
|
|
29
29
|
| "inline-delay" | "awaiting-trigger" | "awaiting-event" | "event-arrived"
|
|
30
30
|
| "no-event" | "replied" | "no-reply"
|
|
31
31
|
// A host action
|
|
@@ -134,6 +134,7 @@ Grouped by what produced it. `kind` and `status` are given as `kind / status`. A
|
|
|
134
134
|
|---|---|---|---|
|
|
135
135
|
| `prompt` or `collect / ok` | none; `llmCalls` set | The speak call answered. The run is asking if fields are still pending, else it moved. | none. This line never carries `next`, even when the run moves on; the lines that follow show where it went |
|
|
136
136
|
| `collect / skipped` | `already-known` | The step was entered and every field it collects was already known (or at `maxAsks`). No call. | `then` |
|
|
137
|
+
| `collect / ok` | `asked-fixed` | The step's `question` went out word for word as its first ask. No call; the run is asking. | |
|
|
137
138
|
| `collect / ok` | none, no `llmCalls` | An asking step whose remaining fields were all known when the customer's next message resumed it: the understand call filled them in from the message, or an action's `ctx.set()` or a tool's `data` had written them since the step last asked. It moved without speaking. | `then` |
|
|
138
139
|
| `collect / skipped` | `max-asks`, `detail` = the field slug | One line per field still unknown when the step moves on because that field reached `maxAsks` (default 3). | |
|
|
139
140
|
| `prompt` or `collect / ok` | `branch` | A branch of the asking step fired: an `if` branch held, or the model answered `when` with true. | the branch's `then` |
|
|
@@ -38,6 +38,7 @@ interface Run {
|
|
|
38
38
|
hop: number;
|
|
39
39
|
startedAt: string;
|
|
40
40
|
suspendedAt?: string;
|
|
41
|
+
staying?: true;
|
|
41
42
|
waiting?: { kind: "timer" | "event"; key?: string; until?: string; setAt: string; event?: string };
|
|
42
43
|
asked: Record<string, number>;
|
|
43
44
|
visits: Record<string, number>;
|
|
@@ -86,6 +87,7 @@ From `src/types/session.ts`. Every date is ISO 8601 text, never a `Date`: the bl
|
|
|
86
87
|
| `hop` | `number` | Chaining depth. `0` for a run a trigger started; `+1` per `{ flow }` move and per `onEnd: 'reset'`. A start at hop 5 is skipped: `code: 'hop-limit'` on `TurnResult.skipped`. |
|
|
87
88
|
| `startedAt` | `string` | The clock's now when the run started. |
|
|
88
89
|
| `suspendedAt` | `string?` | Set while `suspended`. The most recently suspended run is the one that resumes. |
|
|
90
|
+
| `staying` | `true?` | Set once an `onEnd: 'stay'` run has finished its steps and sits on its last talk step, answering every message. Any other move clears it. |
|
|
89
91
|
| `waiting` | object? | Set while `waiting`. See below. |
|
|
90
92
|
| `asked` | `Record<string, number>` | Per field, how many times a talk step spoke with that field still pending. A field at the step's `maxAsks` (default 3, `src/utils/schema.ts`) leaves the pending set: `code: 'max-asks'`, with the field's slug in `detail`. |
|
|
91
93
|
| `visits` | `Record<string, number>` | Per step, how many times this run entered it. Part of every message and action key, so a revisit mints new keys. |
|
package/docs/reference/step.md
CHANGED
|
@@ -44,6 +44,7 @@ The model speaks. A guideline, fields to collect, or both.
|
|
|
44
44
|
```ts fragment
|
|
45
45
|
type TalkStep<C, D> = ({ prompt: Template; collect?: (keyof D & string)[] } | { collect: (keyof D & string)[]; prompt?: Template }) & {
|
|
46
46
|
ask?: Partial<Record<keyof D & string, string>>;
|
|
47
|
+
question?: Template;
|
|
47
48
|
maxAsks?: number;
|
|
48
49
|
branches?: Branch<C, D>[];
|
|
49
50
|
tools?: string[];
|
|
@@ -54,8 +55,9 @@ type TalkStep<C, D> = ({ prompt: Template; collect?: (keyof D & string)[] } | {
|
|
|
54
55
|
| Field | Type | Default | Meaning |
|
|
55
56
|
|---|---|---|---|
|
|
56
57
|
| `prompt` | `Template` | "Collect what is still missing below, in the flow of the conversation, one or two things per message." | The guideline for the reply. Required when there is no `collect`. |
|
|
57
|
-
| `collect` | `(keyof D & string)[]` | none | Fields to
|
|
58
|
+
| `collect` | `(keyof D & string)[]` | none | Fields to ask for now, in this order. The step is done when they are known. The flow's own `collect` lists everything it needs; see [Flow](flow.md). |
|
|
58
59
|
| `ask` | `Partial<Record<slug, string>>` | the field's own `ask` | Per-flow wording for a field. |
|
|
60
|
+
| `question` | `Template` | none | A fixed first question, sent word for word with no model call. Needs `collect`. |
|
|
59
61
|
| `maxAsks` | `number` | `3` | Times a field may be asked before it is skipped (`code: 'max-asks'`, the field in `detail`). |
|
|
60
62
|
| `branches` | `Branch<C, D>[]` | none | Exits judged while the step asks. See [Branches](branches.md). |
|
|
61
63
|
| `tools` | `string[]` | the flow's `tools`, else all | Tools the model may call from this step. |
|
|
@@ -64,6 +66,7 @@ type TalkStep<C, D> = ({ prompt: Template; collect?: (keyof D & string)[] } | {
|
|
|
64
66
|
- Pending fields are `collect` minus the known ones minus those at `maxAsks`, in `collect` order. A step the run enters whose pending list is empty is skipped with no model call (`code: 'already-known'`) and the run follows `then`. When the asking run resumes and the message filled the last field, the same move is logged `ok` with no detail.
|
|
65
67
|
- Reaching a talk step suspends any other run that was asking; this run becomes the asker and holds the floor. It speaks in this turn if the speak call has not happened yet; otherwise it speaks on the next message.
|
|
66
68
|
- The speak call returns the message plus one value per pending field. Values are validated and written; each field still pending is counted as asked once more. With pending fields left the run stays asking. With none left, or with no `collect` at all, the run follows `then` in the same turn.
|
|
69
|
+
- With `question`, the step's first ask is that text, as a `kind: 'verbatim'` message with no speak call (`code: 'asked-fixed'`). It goes out only when every field in `collect` is still pending and none was asked yet, and never on a run that stays (`onEnd: 'stay'`). Reached after this turn's speak call, it goes out in the same turn, the way a `say` does. It counts as one ask of each field. Every later ask is the model's own wording, so a customer who asks something back gets an answer; `maxAsks` still applies. A `{ step, clear }` that clears the step's fields also clears their ask count, so the question goes out again.
|
|
67
70
|
- With `silenced` set, a talk step ends the run with `code: 'silenced'` (`detail` = your reason); a run that was already asking stays asking instead.
|
|
68
71
|
- Outcome kind: `collect` when `collect` is non-empty, else `prompt`.
|
|
69
72
|
|
|
@@ -153,9 +156,9 @@ The code forks. No model call.
|
|
|
153
156
|
| Form | Meaning |
|
|
154
157
|
|---|---|
|
|
155
158
|
| `'passo'` | Jump to that step id. |
|
|
156
|
-
| `'end'` | Finish the run here, exactly as running past the last step does — `onEnd` still decides: `'end'` ends it, `'stay'`
|
|
157
|
-
| `{ step: 'passo', clear: ['campo'] }` | Delete the listed fields from `session.data
|
|
158
|
-
| `{ flow: 'outro', input? }` | End this run (reason `'flow'`) and start `outro` in the same turn, one hop deeper. The child gets `input`, or this run's `input` when absent
|
|
159
|
+
| `'end'` | Finish the run here, exactly as running past the last step does — `onEnd` still decides: `'end'` ends it, `'stay'` goes back to the last talk step and answers every message from there, `'reset'` starts a fresh run. `'end'` is reserved: no step may use it as an id. |
|
|
160
|
+
| `{ step: 'passo', clear: ['campo'] }` | Delete the listed fields from `session.data` and forget how many times the run asked them, then jump. The way to ask something again. |
|
|
161
|
+
| `{ flow: 'outro', input? }` | End this run (reason `'flow'`) and start `outro` in the same turn, one hop deeper. The child gets `input`, or this run's `input` when absent. It holds the floor when this run did, or when no run did: a `mention` flow that chains does not take the message from the run it was routed to. `flow` is a template. |
|
|
159
162
|
|
|
160
163
|
Entering a step counts a visit; the visit is part of every key minted there, so a step visited twice sends twice. A `{ step }` jump to an id that no longer exists ends the run with `code: 'step-gone'`; a `{ flow }` to an unknown flow is skipped with `code: 'flow-gone'`.
|
|
161
164
|
|
package/docs/rfc/v4-one-flow.md
CHANGED
|
@@ -104,6 +104,7 @@ type Trigger<C, D, Cond, E> = { repeat?: Repeat } & ( // default: message/m
|
|
|
104
104
|
|
|
105
105
|
type Talk<C, D, Cond> = ({ prompt: Template; collect?: (keyof D)[] } | { collect: (keyof D)[]; prompt?: Template }) & {
|
|
106
106
|
ask?: Partial<Record<keyof D, string>>; // per-flow wording; schema `ask` is the default
|
|
107
|
+
question?: Template; // fixed first ask, verbatim, no call; needs collect
|
|
107
108
|
maxAsks?: number; branches?: Branch<C, D, Cond>[]; tools?: string[]; instructions?: Instruction<C, D>[];
|
|
108
109
|
};
|
|
109
110
|
|
|
@@ -121,6 +122,7 @@ interface Flow<C, D, Cond, A extends ActionMap, E> {
|
|
|
121
122
|
on?: Trigger<C, D, Cond, E>[]; // absent or [] = Início manual
|
|
122
123
|
anchor?: string; // 'session' (default) or a host anchor name — "vale por conversa / por lead"
|
|
123
124
|
while?: Pred<C, D, Cond>; // re-checked whenever the run moves; default = trigger `if`
|
|
125
|
+
collect?: (keyof D)[]; // the data this flow needs; steps' collect orders the asks
|
|
124
126
|
clearOnStart?: (keyof D)[];
|
|
125
127
|
steps: Step<C, D, Cond, A, E>[]; // ids required, unique, never 'end'
|
|
126
128
|
onEnd?: 'end' | 'stay' | 'reset'; // default 'end'
|
package/examples/05-branches.ts
CHANGED
|
@@ -54,7 +54,7 @@ const agent = f.agent({
|
|
|
54
54
|
then: "dados",
|
|
55
55
|
},
|
|
56
56
|
],
|
|
57
|
-
// After the last step the run
|
|
57
|
+
// After the last step the run goes back to the last talk step it took, so follow-up questions land there.
|
|
58
58
|
onEnd: "stay",
|
|
59
59
|
}),
|
|
60
60
|
f.flow({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@falai/agent",
|
|
3
3
|
"packageManager": "bun@1.4.2",
|
|
4
|
-
"version": "4.0.0-alpha.
|
|
4
|
+
"version": "4.0.0-alpha.13",
|
|
5
5
|
"description": "Conversational state engine for TypeScript where the AI understands, but the code is in control",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/cjs/index.js",
|