docs update to the new stuff
This commit is contained in:
@@ -58,6 +58,20 @@ Typical entry points:
|
|||||||
- `connect(...)`
|
- `connect(...)`
|
||||||
- `compile()`
|
- `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`
|
### `NodeCatalog`
|
||||||
|
|
||||||
LLM-facing registry of available nodes.
|
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
|
2. client LLM selects nodes from the catalog
|
||||||
3. client LLM emits graph structure:
|
3. client LLM emits graph structure:
|
||||||
- node uses
|
- node uses
|
||||||
- mappings
|
- canonical input/output bindings
|
||||||
- conditions
|
- conditions
|
||||||
- foreach nodes
|
- foreach nodes
|
||||||
- interrupt nodes
|
- interrupt nodes
|
||||||
|
|||||||
@@ -22,68 +22,86 @@ future nested execution:
|
|||||||
- nested state reducers can become a differentiator over systems that only merge
|
- nested state reducers can become a differentiator over systems that only merge
|
||||||
whole top-level values
|
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
|
The graph-facing side of a binding is a graph path. The node-facing side is a
|
||||||
node-local path.
|
node-local path. Core stores node use-site wiring as explicit lists of binding
|
||||||
|
objects, not as path-keyed maps.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
in_map:
|
NodeUse.input:
|
||||||
graph source path -> node-local input path
|
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
|
node-local output path -> graph state destination path
|
||||||
```
|
```
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```python
|
```json
|
||||||
in_map = {
|
{
|
||||||
"state.person.name": "user.name",
|
"input": [
|
||||||
"state.digital.email": "user.email",
|
{"target": "user.name", "path": "state.person.name"},
|
||||||
"state.job.title": "job.title",
|
{"target": "user.email", "path": "state.digital.email"},
|
||||||
}
|
{"target": "job.title", "path": "state.job.title"},
|
||||||
|
{"target": "mode", "value": "fast"}
|
||||||
out_map = {
|
],
|
||||||
"job.wage": "state.job.wage",
|
"output": [
|
||||||
"job.years": "state.experience.years",
|
{"source": "job.wage", "target": "state.job.wage"},
|
||||||
"user.age": "state.person.age",
|
{"source": "job.years", "target": "state.experience.years"},
|
||||||
|
{"source": "user.age", "target": "state.person.age"}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Whole-object mapping remains valid:
|
Whole-object mapping remains valid:
|
||||||
|
|
||||||
```python
|
```json
|
||||||
in_map = {"state.person": "user"}
|
{
|
||||||
out_map = {"user": "state.person"}
|
"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:
|
Whole-payload mapping uses the local root path `"."`:
|
||||||
today they are top-level fields; in the target model they may be nested paths.
|
|
||||||
|
```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
|
## Explicitness Rules
|
||||||
|
|
||||||
### Node-local writes must not overlap
|
### 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.
|
be pairwise non-overlapping.
|
||||||
|
|
||||||
Valid:
|
Valid:
|
||||||
|
|
||||||
```python
|
```json
|
||||||
{
|
[
|
||||||
"state.person.name": "user.name",
|
{"target": "user.name", "path": "state.person.name"},
|
||||||
"state.person.email": "user.email",
|
{"target": "user.email", "path": "state.person.email"}
|
||||||
}
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
Invalid:
|
Invalid:
|
||||||
|
|
||||||
```python
|
```json
|
||||||
{
|
[
|
||||||
"state.person": "user",
|
{"target": "user", "path": "state.person"},
|
||||||
"state.person.name": "user.name",
|
{"target": "user.name", "path": "state.person.name"}
|
||||||
}
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
The invalid form would require implicit object patch precedence. Authors must
|
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
|
### State writes must not overlap in one commit
|
||||||
|
|
||||||
`out_map` mutates workflow state. Its destination graph paths must be pairwise
|
`NodeUse.output` mutates workflow state. Its target graph paths must be
|
||||||
non-overlapping inside one logical commit.
|
pairwise non-overlapping inside one logical commit.
|
||||||
|
|
||||||
Invalid:
|
Invalid:
|
||||||
|
|
||||||
```python
|
```json
|
||||||
{
|
[
|
||||||
"user": "state.person",
|
{"source": "user", "target": "state.person"},
|
||||||
"user.name": "state.person.name",
|
{"source": "user.name", "target": "state.person.name"}
|
||||||
}
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
The target model rejects these writes before mutating state.
|
The target model rejects these writes before mutating state.
|
||||||
@@ -115,8 +133,9 @@ target overlaps.
|
|||||||
|
|
||||||
Mapped paths are assertions by the workflow author.
|
Mapped paths are assertions by the workflow author.
|
||||||
|
|
||||||
- a missing graph source path in `in_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 `out_map` 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
|
- optional/default behavior must be modeled explicitly later, not inferred from
|
||||||
a missing path
|
a missing path
|
||||||
|
|
||||||
@@ -140,19 +159,43 @@ creates a reusable boundary for:
|
|||||||
|
|
||||||
## State Declarations and Merge Rules
|
## State Declarations and Merge Rules
|
||||||
|
|
||||||
State merge behavior is attached to declared exact state paths while keeping the
|
State merge behavior is attached to declared exact state paths. The canonical
|
||||||
internal representation flat:
|
schema shape is a list of declarations:
|
||||||
|
|
||||||
```python
|
```json
|
||||||
fields = {
|
{
|
||||||
"person.name": StateField(type="string", reducer="wf.std.replace"),
|
"fields": [
|
||||||
"person.tags": StateField(type="array", reducer="wf.std.append"),
|
{
|
||||||
"profile": StateField(type="object", reducer="wf.std.merge_object"),
|
"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
|
Deprecated dict-shaped state fields are still accepted at parse boundaries:
|
||||||
path-keyed representation.
|
|
||||||
|
```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
|
`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
|
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
|
### Phase 1: Nested node-local mappings
|
||||||
|
|
||||||
- allow nested node-local paths on the destination side of `in_map`
|
- allow nested node-local paths on canonical input binding targets
|
||||||
- allow nested node-local paths on the source side of `out_map`
|
- allow nested node-local paths on canonical output binding sources
|
||||||
- reject overlapping write targets
|
- reject overlapping write targets
|
||||||
- commit node output through a validated state patch
|
- commit node output through a validated state patch
|
||||||
- validate top-level node-local roots statically; validate deeper shape when the
|
- validate top-level node-local roots statically; validate deeper shape when the
|
||||||
existing node schema makes that practical
|
existing node schema makes that practical
|
||||||
|
|
||||||
This phase immediately helps wrappers and structured tools while preserving
|
This phase is implemented in core. It helps wrappers and structured tools while
|
||||||
current state merge behavior.
|
preserving explicit state merge behavior.
|
||||||
|
|
||||||
### Phase 2: Nested declared state paths
|
### Phase 2: Nested declared state paths
|
||||||
|
|
||||||
|
|||||||
@@ -244,11 +244,14 @@ arguments:
|
|||||||
"required": ["text"]
|
"required": ["text"]
|
||||||
},
|
},
|
||||||
"state_schema": {
|
"state_schema": {
|
||||||
"fields": {
|
"fields": [
|
||||||
"echoed": {
|
{
|
||||||
"type": "string"
|
"path": "state.echoed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"output_schema": {
|
"output_schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@@ -389,11 +389,14 @@ Minimal example:
|
|||||||
"required": ["text"]
|
"required": ["text"]
|
||||||
},
|
},
|
||||||
"state_schema": {
|
"state_schema": {
|
||||||
"fields": {
|
"fields": [
|
||||||
"echoed": {
|
{
|
||||||
"type": "string"
|
"path": "state.echoed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"output_schema": {
|
"output_schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
+13
-4
@@ -48,11 +48,14 @@ A minimal draft looks like this:
|
|||||||
"required": ["text"]
|
"required": ["text"]
|
||||||
},
|
},
|
||||||
"state_schema": {
|
"state_schema": {
|
||||||
"fields": {
|
"fields": [
|
||||||
"echoed": {
|
{
|
||||||
"type": "string"
|
"path": "state.echoed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"output_schema": {
|
"output_schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -109,6 +112,12 @@ Both maps are source-to-destination.
|
|||||||
| `in` | graph source path | node-local input path | `"input.text": "message"` |
|
| `in` | graph source path | node-local input path | `"input.text": "message"` |
|
||||||
| `out` | node-local output path | graph state destination path | `"echoed": "state.echoed"` |
|
| `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
|
Graph source paths in `in` normally start with `input.`, `state.`, or
|
||||||
`context.`. Node-local paths do not use those prefixes; they are paths inside
|
`context.`. Node-local paths do not use those prefixes; they are paths inside
|
||||||
the target capability's input or output payload.
|
the target capability's input or output payload.
|
||||||
|
|||||||
Reference in New Issue
Block a user