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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nodus_workflow_ai-0.1.0.tar.gz (14.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nodus_workflow_ai-0.1.0-py3-none-any.whl (10.2 kB view details)

Uploaded Python 3

File details

Details for the file nodus_workflow_ai-0.1.0.tar.gz.

File metadata

  • Download URL: nodus_workflow_ai-0.1.0.tar.gz
  • Upload date:
  • Size: 14.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.9

File hashes

Hashes for nodus_workflow_ai-0.1.0.tar.gz
Algorithm Hash digest
SHA256 14a2536b17f3def736e5ea26ce1435932e5218fcc14f3dc71c9bd8bf01d9f958
MD5 1f7c90af26f98cc4edf72511f1498161
BLAKE2b-256 1a07bda313ed0c8a12cab1f20f32763f7dbc6843706f83edcf07382de7159a70

See more details on using hashes here.

File details

Details for the file nodus_workflow_ai-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for nodus_workflow_ai-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 39db70ac1b98e08772be60a667a017970e542fcda3019757ad731f2faa2e2a43
MD5 c810fa409a762d5d95af8f38c781c998
BLAKE2b-256 d6d6b796f3eb9ee9ef7abb9919ba5ea975d7dd7960a5a0e42175fced361db2fb

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 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