Skip to content

Add graph validation/compilation #67

Open

Description

@elijahbenizzy

We should be able to validate the DAG. Specific things we can check for:

  1. For all nodes that read state item X, given an initial state, does there exist a path that does not provide the required state?
  2. For all produced keys, do types match up? (when we have typing)?
  3. Is there a path that will not end up in a valid state (E.G. nowhere to go)?

Then on execution, we should be able to say:

  1. If there is a halt_after, is there a path that will reach it?
  2. If there is a halt_before, is there a path that will reach it?

Idea for (1):

app = ....build(validate=True) # on build, we can validate
app = ....build(validate_types=True, validate_paths=True) # if you want to do different things

Idea for (2)

app = ....build()
app.run(halt_after=..., validate=True)

Note this is not friendly towards state with default values as we don't expose that in the framework... But I think types + defaults should be easy enough to expose.

Activity

  1. added
    area/coreApplication, State, Graph, Actions
    and removed on Mar 28, 2026
  2. andreahlert commented on Mar 28, 2026

    @andreahlert
    Collaborator

    Labels migrated to new taxonomy (#692).

  3. YongGoose commented on Nov 11, 2026

    @YongGoose
    Member

    I opened #1038 for the first check here: an opt-in build(validate=True) / abuild(validate=True) that raises when an action or a transition condition reads a state key that no action writes and the initial state does not provide. Graph.validate() returns the same list without raising.

    To see whether it catches real mistakes, I ran it over the example apps (18 of them build locally; the rest need extra dependencies). It flagged two, both genuine declaration errors:

    examples/llm-adventure-game/application.py
      - Action `win` reads ['challenges'], but no action writes them and they are not in the initial state.
    
    examples/simulation/application.py
      - Action `forecast` reads ['current_timestep'], but no action writes them and they are not in the initial state.
      - Condition `current_timestep<simulation_end` on transition `forecast` -> `forecast` reads ['current_timestep'], but no action writes them and they are not in the initial state.
      - Condition `current_timestep>=simulation_end` on transition `forecast` -> `terminate` reads ['current_timestep'], but no action writes them and they are not in the initial state.
    
    • win declares a module-level list as a state read.
    • forecast writes current_timestep without declaring it in writes.

    Both examples still run, because reads/writes aren't enforced at runtime. That's also why the check is off by default: turning it on would break apps like these. #1038 fixes both examples.

    What I left out, and why:

    • Path-sensitive check (point 1 as written: "does there exist a path that does not provide the required state?"). I prototyped it. On the examples it produced 3 hits, all false positives: the actions guard those reads with state.get(...) or an earlier branch. I think it needs a way to declare optional reads on actions first (conditions already have optional_keys).
    • Type checks (point 2).
    • halt_before/halt_after reachability.

    Two questions:

    1. Should this stay opt-in, or become a default warning at some point?
    2. Would optional reads on actions (e.g. @action(reads=[...], optional_reads=[...])) be welcome as a follow-up, to unlock the path-sensitive check?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/coreApplication, State, Graph, Actionskind/featureNet-new functionalitypriority/lowNice-to-have, backlog

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions