Continues from README.md: node kinds, binding to Rust,
the state model, routes, policies, comments/strings, safety, examples,
formatting, testkit, and implementation milestones.
Initial built-in node kinds:
Uses the harness agent loop or one model call depending on config.
Supported fields:
modelsystemprompttoolsroutesretrytimeout
Single model invocation. Does not automatically execute tools.
Supported fields:
modelsystempromptroutesretrytimeout
Executes tool calls already present in state.
Supported fields:
toolsnextretrytimeout
Routes based on a named route function provided from Rust.
Supported fields:
routesmetadata
Calls another compiled graph.
Supported fields:
graphnextroutes
Calls a registered harness agent as a graph node.
Supported fields:
agentinputnextroutesretrytimeoutsteering
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.
Runs a registered REPL script or model-driven CodeAct loop through the harness REPL runtime.
Supported fields:
modelscripttoolsroutesretrytimeout
Emits a resumable human-in-the-loop interrupt.
Supported fields:
promptoptionsroutesmetadata
Waits for named upstream nodes or barrier channels before continuing.
Supported fields:
sourcesnexttimeout
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.
Version 1 should keep state Rust-owned. The language can refer to standard channels by convention and bind them to registered reducers:
messagestool_callsstructured_responsemetadataartifactscandidatesusageinterrupts
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 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
routesornext, not both ENDis reserved
Future typed route support can generate Rust enums from route declarations.
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:
// line comment
Strings:
system "single line"
prompt """
multi-line prompt
"""
Multi-line strings should preserve content exactly except for one predictable dedent rule.
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
graph summarize {
start model
node model {
kind model
model "default"
system "Summarize the user request."
next END
}
}
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
}
}
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
}
}
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.
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
- token model
- spans
- lexer
- parser for graph, start, node, next, routes
- structured diagnostics
- duplicate node validation
- unknown route target validation
- compile topology into
GraphBuilder - support
kind model - support
kind agent - bind model names
- validate tool names
- compile
tool_executor - add agent/tool graph example
- parse timeout and retry
- bind subgraphs
- compile node policies
- parse channel declarations
- bind reducer registry entries
- lower
commandandsends - compile join/barrier nodes
- 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
- stable formatting
- golden tests