Skip to main content

nodus-workflow-ai

Run a generated plan as a Nodus graph: validated as a whole before any step runs, under a capability grant narrowed to what the plan declared.

Design record: docs/design/v5/07-generated-plans.md in the nodus-lang repo. Issue: #93.

What this is not

  • Not a planner. nodus-agent has two (LocalPlanner, LLMPlanner).
  • Not an executor. task / graph / run_graph already run a graph built from data at runtime.
  • Not a loop. goal … over … until is the loop.

The execution substrate already existed. What was missing was the bridge from a planner's flat list of steps to a DAG, and a contract for what is checked before a generated plan is allowed to run.

Usage

from nodus import NodusRuntime
from nodus_agent import LocalPlanner
from nodus_workflow_ai import validate_plan, run_plan, PlanRejected

raw = LocalPlanner().plan("fetch then analyze", tools, context)

try:
    plan = validate_plan(raw, available_tools=[t["name"] for t in tools])
except PlanRejected as rejected:
    for finding in rejected.findings:
        print("rejected:", finding)
else:
    outcome = run_plan(plan, NodusRuntime(timeout_ms=None))
    print(outcome["result"]["steps"])      # {"fetch": ..., "analyze": ...}
    print(outcome["parallel_groups"])      # [["fetch"], ["analyze"]]

The two things that carry the weight

Validation happens before anything runs

A declared workflow is checked by the compiler. A generated one has no compiler, so the checks have to be somewhere, and "the model was careful" is not somewhere.

A plan that will fail at step 7 because a tool does not exist is rejected at step 0. The run has not touched the world yet; after step 1 it has.

Checked: every named tool exists, every tool is permitted (when the host says what it permits), the plan is under its step ceiling, names are unique, and every after names a step that is actually in the plan.

Not checked, deliberately: acyclicity and dependency resolution, which come free from construction — a dependency is a task value, so you cannot reference one you do not have, and closing a cycle would need a forward reference. A second implementation of a guarantee the substrate already gives is the one that drifts.

The grant is narrowed to the plan

A generated plan runs under the tools it declared, not the host's ambient policy. The plan names its tools before it runs; that list is exactly the grant it needs. A plan attempting a tool it did not declare is refused — not because the tool is forbidden in general, but because this plan did not ask for it.

That is a real strengthening over "run the plan with the host's policy", and it costs nothing to compute: the tool list is already being collected for the existence check.

Dependency edges

A planner emits a flat list; a workflow is a DAG. Something has to decide the edges.

the planner said this step depends on
"after": ["fetch"] exactly that
"after": [] nothing — a declared root
nothing at all the step before it

An explicit empty list and an absent key are different statements. Collapsing them would make a planner unable to declare parallelism without also annotating every step it forgot.

The fallback is per step, not per plan: a plan may annotate some steps and not others, and falling back wholesale because one step was silent would discard the edges the planner did emit.

Edges are not inferred from data flow. That is where this wants to end up, and it needs a plan format declaring each step's inputs and outputs, which PlannerBackend does not have — a change to a published package's protocol, and a second decision rather than a prerequisite.

What this does not promise

A generated plan's goal is not statically satisfiable. For a declared goal, reached("label") is checked against checkpoint literals in the source. A generated plan has no literals — the predicate and the steps both come from the model, so checking one against the other proves only that the model was self-consistent. Synthesising labels to make the existing check pass would give a check that cannot fail, which reads as a guarantee and is worse than an absent one. The budget bounds the loop; nothing else does.

A generated run is not resumable across processes. A declared workflow rebuilds from source on resume; a generated graph was built from a value that existed in one process. This is the one place a generated workflow is genuinely weaker than a declared one, and it should not be discovered by a user.

A wrong edge is a real failure mode. A model asserting after: ["analyze"] on a step that does not need it costs parallelism; asserting the reverse costs correctness. Validation bounds what a plan can name; it cannot judge whether an edge is right.

Requirements

nodus-lang >= 5.8.0 — step names on task() landed in #679, and without them a generated run reports an empty steps map.

nodus-agent is optional, and only for its planners.

Tests

pytest tests/ -q

Metadata

Release files for nodus-workflow-ai 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nodus-workflow-ai 0.1.0
File Size Uploaded
nodus_workflow_ai-0.1.0.tar.gz 14.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nodus-workflow-ai 0.1.0
File Interpreter ABI Platform
nodus_workflow_ai-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 24.9 kB

Release files / nodus_workflow_ai-0.1.0.tar.gz

Download URL nodus_workflow_ai-0.1.0.tar.gz
Size 14.7 kB
Tags Source
SHA-256 checksum
How to use checksums
14a2536b17f3def736e5ea26ce1435932e5218fcc14f3dc71c9bd8bf01d9f958
BLAKE2b-256 checksum
How to use checksums
1a07bda313ed0c8a12cab1f20f32763f7dbc6843706f83edcf07382de7159a70
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release files / nodus_workflow_ai-0.1.0-py3-none-any.whl

Download URL nodus_workflow_ai-0.1.0-py3-none-any.whl
Size 10.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
39db70ac1b98e08772be60a667a017970e542fcda3019757ad731f2faa2e2a43
BLAKE2b-256 checksum
How to use checksums
d6d6b796f3eb9ee9ef7abb9919ba5ea975d7dd7960a5a0e42175fced361db2fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page