docs update to the new stuff
This commit is contained in:
@@ -58,6 +58,20 @@ Typical entry points:
|
||||
- `connect(...)`
|
||||
- `compile()`
|
||||
|
||||
`in_map` and `out_map` remain authoring convenience parameters. The compiled
|
||||
core `NodeUse` shape is canonical `input` and `output` binding lists:
|
||||
|
||||
```json
|
||||
{
|
||||
"input": [{"target": "text", "path": "input.text"}],
|
||||
"output": [{"source": "echoed", "target": "state.echoed"}]
|
||||
}
|
||||
```
|
||||
|
||||
Authoring should not teach client LLMs that `in_map`, `input_values`, or
|
||||
`out_map` are the preferred core model. Those names are compatibility inputs
|
||||
and Python-builder sugar.
|
||||
|
||||
### `NodeCatalog`
|
||||
|
||||
LLM-facing registry of available nodes.
|
||||
@@ -90,7 +104,7 @@ that references those known nodes. It should not usually generate new raw
|
||||
2. client LLM selects nodes from the catalog
|
||||
3. client LLM emits graph structure:
|
||||
- node uses
|
||||
- mappings
|
||||
- canonical input/output bindings
|
||||
- conditions
|
||||
- foreach nodes
|
||||
- interrupt nodes
|
||||
|
||||
@@ -22,68 +22,86 @@ future nested execution:
|
||||
- nested state reducers can become a differentiator over systems that only merge
|
||||
whole top-level values
|
||||
|
||||
## Canonical Mapping Rule
|
||||
## Canonical Binding Rule
|
||||
|
||||
The graph-facing side of a map is a graph path. The node-facing side is a
|
||||
node-local path.
|
||||
The graph-facing side of a binding is a graph path. The node-facing side is a
|
||||
node-local path. Core stores node use-site wiring as explicit lists of binding
|
||||
objects, not as path-keyed maps.
|
||||
|
||||
```text
|
||||
in_map:
|
||||
graph source path -> node-local input path
|
||||
NodeUse.input:
|
||||
path binding: graph source path -> node-local input path
|
||||
value binding: JSON value -> node-local input path
|
||||
|
||||
out_map:
|
||||
NodeUse.output:
|
||||
node-local output path -> graph state destination path
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```python
|
||||
in_map = {
|
||||
"state.person.name": "user.name",
|
||||
"state.digital.email": "user.email",
|
||||
"state.job.title": "job.title",
|
||||
}
|
||||
|
||||
out_map = {
|
||||
"job.wage": "state.job.wage",
|
||||
"job.years": "state.experience.years",
|
||||
"user.age": "state.person.age",
|
||||
```json
|
||||
{
|
||||
"input": [
|
||||
{"target": "user.name", "path": "state.person.name"},
|
||||
{"target": "user.email", "path": "state.digital.email"},
|
||||
{"target": "job.title", "path": "state.job.title"},
|
||||
{"target": "mode", "value": "fast"}
|
||||
],
|
||||
"output": [
|
||||
{"source": "job.wage", "target": "state.job.wage"},
|
||||
{"source": "job.years", "target": "state.experience.years"},
|
||||
{"source": "user.age", "target": "state.person.age"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Whole-object mapping remains valid:
|
||||
|
||||
```python
|
||||
in_map = {"state.person": "user"}
|
||||
out_map = {"user": "state.person"}
|
||||
```json
|
||||
{
|
||||
"input": [{"target": "user", "path": "state.person"}],
|
||||
"output": [{"source": "user", "target": "state.person"}]
|
||||
}
|
||||
```
|
||||
|
||||
The change from the current implementation is only on the node-local sides:
|
||||
today they are top-level fields; in the target model they may be nested paths.
|
||||
Whole-payload mapping uses the local root path `"."`:
|
||||
|
||||
```json
|
||||
{
|
||||
"input": [{"target": ".", "path": "state.rates"}],
|
||||
"output": [{"source": ".", "target": "state.rates"}]
|
||||
}
|
||||
```
|
||||
|
||||
Deprecated compatibility inputs are still accepted at model-parse boundaries:
|
||||
`in_map`, `input_values`, and `out_map`. Validated `NodeUse` models store and
|
||||
dump only canonical `input` and `output` bindings. `wf_authoring` may still
|
||||
accept `in_map` and `out_map` as builder convenience parameters, but it compiles
|
||||
them into canonical bindings.
|
||||
|
||||
## Explicitness Rules
|
||||
|
||||
### Node-local writes must not overlap
|
||||
|
||||
`in_map` constructs node input payloads. Its destination node-local paths must
|
||||
`NodeUse.input` constructs node input payloads. Its target node-local paths must
|
||||
be pairwise non-overlapping.
|
||||
|
||||
Valid:
|
||||
|
||||
```python
|
||||
{
|
||||
"state.person.name": "user.name",
|
||||
"state.person.email": "user.email",
|
||||
}
|
||||
```json
|
||||
[
|
||||
{"target": "user.name", "path": "state.person.name"},
|
||||
{"target": "user.email", "path": "state.person.email"}
|
||||
]
|
||||
```
|
||||
|
||||
Invalid:
|
||||
|
||||
```python
|
||||
{
|
||||
"state.person": "user",
|
||||
"state.person.name": "user.name",
|
||||
}
|
||||
```json
|
||||
[
|
||||
{"target": "user", "path": "state.person"},
|
||||
{"target": "user.name", "path": "state.person.name"}
|
||||
]
|
||||
```
|
||||
|
||||
The invalid form would require implicit object patch precedence. Authors must
|
||||
@@ -91,16 +109,16 @@ choose either whole-object mapping or explicit child mapping.
|
||||
|
||||
### State writes must not overlap in one commit
|
||||
|
||||
`out_map` mutates workflow state. Its destination graph paths must be pairwise
|
||||
non-overlapping inside one logical commit.
|
||||
`NodeUse.output` mutates workflow state. Its target graph paths must be
|
||||
pairwise non-overlapping inside one logical commit.
|
||||
|
||||
Invalid:
|
||||
|
||||
```python
|
||||
{
|
||||
"user": "state.person",
|
||||
"user.name": "state.person.name",
|
||||
}
|
||||
```json
|
||||
[
|
||||
{"source": "user", "target": "state.person"},
|
||||
{"source": "user.name", "target": "state.person.name"}
|
||||
]
|
||||
```
|
||||
|
||||
The target model rejects these writes before mutating state.
|
||||
@@ -115,8 +133,9 @@ target overlaps.
|
||||
|
||||
Mapped paths are assertions by the workflow author.
|
||||
|
||||
- a missing graph source path in `in_map` is a runtime error
|
||||
- a missing node-local output path in `out_map` is a runtime error
|
||||
- a missing graph source path in an input path binding is a runtime error
|
||||
- a missing node-local output path in an output binding is a runtime error
|
||||
- an explicit `null` value is different from a missing path
|
||||
- optional/default behavior must be modeled explicitly later, not inferred from
|
||||
a missing path
|
||||
|
||||
@@ -140,19 +159,43 @@ creates a reusable boundary for:
|
||||
|
||||
## State Declarations and Merge Rules
|
||||
|
||||
State merge behavior is attached to declared exact state paths while keeping the
|
||||
internal representation flat:
|
||||
State merge behavior is attached to declared exact state paths. The canonical
|
||||
schema shape is a list of declarations:
|
||||
|
||||
```python
|
||||
fields = {
|
||||
"person.name": StateField(type="string", reducer="wf.std.replace"),
|
||||
"person.tags": StateField(type="array", reducer="wf.std.append"),
|
||||
"profile": StateField(type="object", reducer="wf.std.merge_object"),
|
||||
```json
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"path": "state.person.name",
|
||||
"schema": {"type": "string"},
|
||||
"reducer": {"name": "wf.std.replace"}
|
||||
},
|
||||
{
|
||||
"path": "state.person.tags",
|
||||
"schema": {"type": "array"},
|
||||
"reducer": {"name": "wf.std.append"}
|
||||
},
|
||||
{
|
||||
"path": "state.profile",
|
||||
"schema": {"type": "object"},
|
||||
"reducer": {"name": "wf.std.merge_object"}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Presentation layers may rebuild a tree for humans. Core should keep the simpler
|
||||
path-keyed representation.
|
||||
Deprecated dict-shaped state fields are still accepted at parse boundaries:
|
||||
|
||||
```json
|
||||
{
|
||||
"fields": {
|
||||
"person.name": {"type": "string", "reducer": "wf.std.replace"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Validated `StateSchema` models store and dump the canonical list shape.
|
||||
Presentation layers may rebuild a tree for humans.
|
||||
|
||||
`wf_authoring` keeps authored schemas nested for humans and LLM clients, but
|
||||
projects nested authored state into this flat exact-path index. For example, a
|
||||
@@ -242,15 +285,15 @@ Examples a future reducer library could support:
|
||||
|
||||
### Phase 1: Nested node-local mappings
|
||||
|
||||
- allow nested node-local paths on the destination side of `in_map`
|
||||
- allow nested node-local paths on the source side of `out_map`
|
||||
- allow nested node-local paths on canonical input binding targets
|
||||
- allow nested node-local paths on canonical output binding sources
|
||||
- reject overlapping write targets
|
||||
- commit node output through a validated state patch
|
||||
- validate top-level node-local roots statically; validate deeper shape when the
|
||||
existing node schema makes that practical
|
||||
|
||||
This phase immediately helps wrappers and structured tools while preserving
|
||||
current state merge behavior.
|
||||
This phase is implemented in core. It helps wrappers and structured tools while
|
||||
preserving explicit state merge behavior.
|
||||
|
||||
### Phase 2: Nested declared state paths
|
||||
|
||||
|
||||
@@ -244,11 +244,14 @@ arguments:
|
||||
"required": ["text"]
|
||||
},
|
||||
"state_schema": {
|
||||
"fields": {
|
||||
"echoed": {
|
||||
"type": "string"
|
||||
"fields": [
|
||||
{
|
||||
"path": "state.echoed",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
|
||||
@@ -389,11 +389,14 @@ Minimal example:
|
||||
"required": ["text"]
|
||||
},
|
||||
"state_schema": {
|
||||
"fields": {
|
||||
"echoed": {
|
||||
"type": "string"
|
||||
"fields": [
|
||||
{
|
||||
"path": "state.echoed",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
|
||||
+13
-4
@@ -48,11 +48,14 @@ A minimal draft looks like this:
|
||||
"required": ["text"]
|
||||
},
|
||||
"state_schema": {
|
||||
"fields": {
|
||||
"echoed": {
|
||||
"type": "string"
|
||||
"fields": [
|
||||
{
|
||||
"path": "state.echoed",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
@@ -109,6 +112,12 @@ Both maps are source-to-destination.
|
||||
| `in` | graph source path | node-local input path | `"input.text": "message"` |
|
||||
| `out` | node-local output path | graph state destination path | `"echoed": "state.echoed"` |
|
||||
|
||||
Draft `in` and `out` maps are an authoring-layer shape. When a draft is
|
||||
compiled, the core workflow uses canonical `NodeUse.input` and
|
||||
`NodeUse.output` binding lists. The old core `in_map`, `input_values`, and
|
||||
`out_map` fields are parse-only compatibility inputs, not the preferred saved
|
||||
shape.
|
||||
|
||||
Graph source paths in `in` normally start with `input.`, `state.`, or
|
||||
`context.`. Node-local paths do not use those prefixes; they are paths inside
|
||||
the target capability's input or output payload.
|
||||
|
||||
Reference in New Issue
Block a user