Expressive Language: Node Kinds, Binding, State, Routes, Examples

July 4, 2026 ยท View on GitHub

Continues from README.md: node kinds, binding to Rust, the state model, routes, policies, comments/strings, safety, examples, formatting, testkit, and implementation milestones.

Node Kinds

Initial built-in node kinds:

agent

Uses the harness agent loop or one model call depending on config.

Supported fields:

  • model
  • system
  • prompt
  • tools
  • routes
  • retry
  • timeout

model

Single model invocation. Does not automatically execute tools.

Supported fields:

  • model
  • system
  • prompt
  • routes
  • retry
  • timeout

tool_executor

Executes tool calls already present in state.

Supported fields:

  • tools
  • next
  • retry
  • timeout

router

Routes based on a named route function provided from Rust.

Supported fields:

  • routes
  • metadata

subgraph

Calls another compiled graph.

Supported fields:

  • graph
  • next
  • routes

subagent

Calls a registered harness agent as a graph node.

Supported fields:

  • agent
  • input
  • next
  • routes
  • retry
  • timeout
  • steering

Example:

node research {
  kind subagent
  agent "researcher"
  steering {
    parent allow ["add_instruction", "request_status", "cancel"]
    human allow ["add_instruction", "pause", "resume", "cancel"]
    delivery "safe_boundary"
  }
  next synthesize
}

Steering policies lower into harness steering policy and graph task policy. They can narrow a child agent's model/tool/runtime limits but cannot grant capabilities absent from the registry or parent run policy.

repl_agent

Runs a registered REPL script or model-driven CodeAct loop through the harness REPL runtime.

Supported fields:

  • model
  • script
  • tools
  • routes
  • retry
  • timeout

interrupt

Emits a resumable human-in-the-loop interrupt.

Supported fields:

  • prompt
  • options
  • routes
  • metadata

join

Waits for named upstream nodes or barrier channels before continuing.

Supported fields:

  • sources
  • next
  • timeout

Binding To Rust

The language should not define arbitrary Rust closures. Instead, it should bind to Rust-provided registries:

let workflow = LanguageCompiler::new()
    .with_models(models)
    .with_tools(tools)
    .with_node_templates(templates)
    .compile_source("support.rag", source)?;

Registries:

  • model registry
  • tool registry
  • agent registry
  • node template registry
  • route function registry
  • reducer registry
  • graph registry for subgraphs
  • store registry
  • middleware registry
  • REPL script registry

When a graph is generated by a REPL session, the session may call the compiler with source text or an AST, but the compiler must use the same registries and policy checks. Generated source can request capabilities only from the allowed set attached to the parent run or registry namespace.

This keeps source files declarative and prevents unsafe dynamic execution.

State Model

Version 1 should keep state Rust-owned. The language can refer to standard channels by convention and bind them to registered reducers:

  • messages
  • tool_calls
  • structured_response
  • metadata
  • artifacts
  • candidates
  • usage
  • interrupts

Example:

channel messages messages
channel candidates append
channel usage aggregate "usage_delta"
channel review overwrite

Future versions may add state schema declarations:

state SupportState {
  messages: messages append
  customer_id: string overwrite
  ticket_id: string? overwrite
}

State schemas should be delayed until reducer-based graph execution exists.

Routes

Routes are named outcomes.

routes {
  tool_call -> tools
  final -> END
  escalate -> human_review
}

Rules:

  • route names are unique per node
  • route targets must exist or be END
  • route names are ASCII identifiers
  • a node may use routes or next, not both
  • END is reserved

Future typed route support can generate Rust enums from route declarations.

Policies

Node-level policies:

node agent {
  timeout 30s
  retry {
    max_attempts: 3
    backoff: "exponential"
  }
}

Graph-level defaults:

graph support_agent {
  defaults {
    timeout 60s
    recursion_limit 50
  }
}

Policies lower into graph node policies and harness request policies.

Comments And Strings

Comments:

// line comment

Strings:

system "single line"

prompt """
multi-line prompt
"""

Multi-line strings should preserve content exactly except for one predictable dedent rule.

Safety

The language must be safe to parse and validate from untrusted text.

Safety rules:

  • no arbitrary code execution
  • no filesystem access from language source
  • no network access from language source
  • no dynamic provider lookup without registry binding
  • no environment variable interpolation in v1
  • bounded parser recursion
  • bounded source size

Examples

Minimal Model Graph

graph summarize {
  start model

  node model {
    kind model
    model "default"
    system "Summarize the user request."
    next END
  }
}

Agent With Tools

graph support_agent {
  start agent

  node agent {
    kind agent
    model "default"
    system "Resolve support requests using tools when useful."
    tools ["lookup_user", "create_ticket"]
    routes {
      tool_call -> tools
      final -> END
    }
  }

  node tools {
    kind tool_executor
    next agent
  }
}

Human Review

graph approval_flow {
  start draft

  node draft {
    kind model
    model "default"
    system "Draft a response."
    next review
  }

  node review {
    kind interrupt
    prompt "Approve this response?"
    routes {
      approved -> send
      rejected -> draft
    }
  }

  node send {
    kind tool_executor
    tools ["send_email"]
    next END
  }
}

Formatting

Formatter goals:

  • stable ordering within declarations
  • preserve comments
  • normalize indentation to two spaces
  • one item per line for lists longer than one entry
  • avoid rewriting prompt body content

The formatter can come after parser and diagnostics.

Testkit

language::testkit should include:

  • parse snapshot helper
  • diagnostic snapshot helper
  • compile helper with fake registries
  • golden source fixtures
  • round-trip formatter tests once formatter exists

Implementation Milestones

L1: Parser Skeleton

  • token model
  • spans
  • lexer
  • parser for graph, start, node, next, routes

L2: Diagnostics

  • structured diagnostics
  • duplicate node validation
  • unknown route target validation

L3: Compiler Preview

  • compile topology into GraphBuilder
  • support kind model
  • support kind agent
  • bind model names

L4: Tool Binding

  • validate tool names
  • compile tool_executor
  • add agent/tool graph example

L5: Policies And Subgraphs

  • parse timeout and retry
  • bind subgraphs
  • compile node policies

L6: Channels, Commands, And Fanout

  • parse channel declarations
  • bind reducer registry entries
  • lower command and sends
  • compile join/barrier nodes

L7: Agent-Authored Graphs

  • compile generated source under parent run policy
  • require review gates when policy marks generated graphs as untrusted
  • store generated blueprint provenance
  • expose graph diff and preview diagnostics

L8: Formatter

  • stable formatting
  • golden tests