6.4 KiB
Structural Graph Paths Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Save canonical graph paths as structural objects while keeping old dotted strings as parse-only compatibility input.
Architecture: The core already has first-class path types: GraphSourcePath, StatePath, and LocalPath. Update those Pydantic hooks to accept structural dict input and serialize structurally in JSON mode. Keep str(path) for display and legacy fields. Do not redesign NodeUse.input / output; those structs already replaced deprecated in_map / out_map.
Tech Stack: Python 3.14, Pydantic core schema hooks, wf_core.paths, wf_core.models.steps, pytest.
Current State
Canonical node bindings already exist:
{
"input": [{ "path": "input.message", "target": "message" }],
"output": [{ "source": "echoed", "target": "state.echoed" }]
}
Internally these parse to:
GraphSourcePathLocalPathStatePath
The remaining problem is serialization. These path objects currently dump as strings, so saved JSON still relies on dot-separated path grammar.
Canonical Shape
Graph source paths:
{ "root": "state", "parts": ["person", "name"] }
State write paths:
{ "root": "state", "parts": ["person", "name"] }
Local node paths:
{ "root": "local", "parts": ["payload", "text"] }
Local root remains explicit:
{ "root": "local", "parts": [] }
Old strings such as "state.person.name" and "." remain accepted input.
Task 1: Add Structural Serialization for Path Types
Files:
-
Modify:
src/wf_core/paths.py -
Test:
tests/core/test_path_values.py -
Step 1: Update tests first
Change test_pydantic_accepts_path_strings_and_serializes_strings into structural JSON expectations:
dumped = payload.model_dump(mode="json")
assert dumped["source"] == {"root": "input", "parts": ["user"]}
assert dumped["target"] == {"root": "state", "parts": ["person"]}
assert dumped["local"] == {"root": "local", "parts": ["user"]}
Keep model_dump() expectations if useful for Python-mode compatibility only if the implementation intentionally keeps Python mode as strings. Otherwise assert structural dumps in both modes.
- Step 2: Add structural input tests
Add a test:
payload = Payload.model_validate({
"source": {"root": "input", "parts": ["user.name"]},
"target": {"root": "state", "parts": ["person.name"]},
"local": {"root": "local", "parts": ["payload.text"]},
})
assert payload.source == GraphSourcePath.input("user.name")
assert payload.target == StatePath.of("person.name")
assert payload.local == LocalPath.of("payload.text")
This documents that structural parts are literal field names. Old string inputs still split on dots for compatibility, but structural parts such as "user.name" are not split again.
- Step 3: Implement path serializers
In src/wf_core/paths.py, update each path type:
LocalPathaccepts string, object instance, and dict{"root": "local", "parts": list[str]}GraphSourcePathaccepts string, object instance, and dict{"root": "input"|"state"|"context", "parts": list[str]}StatePathaccepts string, object instance, and dict{"root": "state", "parts": list[str]}
Serialize as dicts in JSON mode:
{"root": "local", "parts": list(value.parts)}
{"root": value.root, "parts": list(value.parts)}
{"root": "state", "parts": list(value.parts)}
- Step 4: Run focused path tests
Run:
uv run --with pytest pytest tests/core/test_path_values.py -q
Expected: all tests pass.
Task 2: Update Canonical Node Binding Dumps
Files:
-
Test:
tests/core/test_canonical_node_bindings.py -
Test:
tests/authoring/test_builder.py -
Step 1: Update canonical node dump expectations
In tests/core/test_canonical_node_bindings.py, update JSON-mode expectations:
assert dumped["input"][1]["path"] == {"root": "input", "parts": ["message"]}
assert dumped["input"][1]["target"] == {"root": "local", "parts": ["message"]}
assert dumped["output"][0]["source"] == {"root": "local", "parts": ["echoed"]}
assert dumped["output"][0]["target"] == {"root": "state", "parts": ["echoed"]}
Deprecated in_map / out_map inputs should continue parsing, but dumps must omit those old fields and emit structural paths.
- Step 2: Update authoring serialization expectations
In tests/authoring/test_builder.py, update any model_dump(mode="json") expectations that currently assert path strings.
- Step 3: Run focused binding/authoring tests
Run:
uv run --with pytest pytest tests/core/test_canonical_node_bindings.py tests/authoring/test_builder.py -q
Expected: all tests pass.
Task 3: Update Docs
Files:
-
Modify:
docs/structural_refs.md -
Modify: any path/core docs if directly relevant.
-
Step 1: Add graph path note
Extend the path note in docs/structural_refs.md:
New canonical graph path JSON uses root/parts objects. Old strings are accepted
at parse boundaries for compatibility.
- Step 2: Add examples
Include examples:
{"root": "input", "parts": ["message"]}
{"root": "state", "parts": ["echoed"]}
{"root": "local", "parts": []}
Task 4: Verification
- Step 1: Run focused tests
uv run --with pytest pytest tests/core/test_path_values.py tests/core/test_canonical_node_bindings.py tests/authoring/test_builder.py -q
- Step 2: Run full tests
uv run --with pytest pytest -q
- Step 3: Run checks
uvx ruff check src/wf_core/paths.py tests/core/test_path_values.py tests/core/test_canonical_node_bindings.py tests/authoring/test_builder.py
uv run basedpyright --level error src/wf_core/paths.py tests/core/test_path_values.py tests/core/test_canonical_node_bindings.py tests/authoring/test_builder.py
Self-Review Notes
- This plan does not revive
in_map/out_map; those remain deprecated parse-only fields. - This plan relaxes path segment validation. Structural
partspreserve literal field names, including dots and spaces. Old dotted string inputs still split on dots for compatibility. - This plan changes saved JSON shape for canonical path fields, so broad tests are required.