Graft v3.3: Auto-Import Code Actions and Runtime Conditional Routing
Graft is a graph-native language for AI agent harness engineering. It compiles .gft source files into execution harnesses — declaring how agents communicate, what context they share, and how token budgets flow through a pipeline. The compiler is built with a multi-agent adversarial debate process where 2-4 AI agents independently analyze, cross-critique, and converge on implementations.
v3.3 fills two significant gaps: the LSP gains its first code action (auto-import), and conditional edges — which have parsed and analyzed since v1.3 — finally execute at runtime. Two smaller additions round out the version: document symbols for outline views and compile-time condition type validation.
What's New
LSP Code Actions: Auto-Import
When you reference an undefined name in a .gft file, the LSP now offers a quick fix to import it:
node Worker(model: sonnet, budget: 5k/2k) {
reads: [UserMessage] // ⚠ 'UserMessage' is not defined
produces Result { answer: String }
}
Clicking the quick fix inserts the import statement automatically:
import { UserMessage } from "./shared.gft"
node Worker(model: sonnet, budget: 5k/2k) {
reads: [UserMessage] // ✓ resolved
produces Result { answer: String }
}
The implementation uses a workspace export cache that lazily scans all .gft files in the workspace on first code action request, then updates incrementally as files change. The cache maps export names to file paths, and computeRelativeImportPath() handles cross-platform path resolution (including Windows backslash normalization).
Conditional Edge Runtime Routing
Conditional edges have been part of the Graft grammar since v1.3, but they were ignored at runtime — the flow runner only followed direct node references. Now they evaluate:
edge Classifier -> Reviewer when(category == "critical")
edge Classifier -> Logger when(category == "info")
edge Classifier -> DefaultHandler else
At runtime, evaluateCondition() checks each when branch against the source node's output. The first matching condition wins; else serves as the default fallback. All six comparison operators work (==, !=, >=, >, <=, <), with numeric coercion for ordered comparisons.
Document Symbols
The LSP now provides document symbols for .gft files, enabling outline views in editors:
- Contexts →
Class - Nodes →
Function - Memories →
Variable - Graphs →
Module - Edges →
Event
Condition Type Validation
A long-standing TODO (deferred since v1.0): ordered comparison operators on non-numeric fields now produce a compile-time error.
// ✗ TYPE_CONDITION_MISMATCH: '>=' requires numeric field, got String
edge Router -> Handler when(status >= "active")
// ✓ OK: '==' works on any type
edge Router -> Handler when(status == "active")
The Diagnostic Location Bug
The most interesting finding from the A2/A3 debate on code actions: A3-Skeptic discovered that 6 out of 9 SCOPE_UNDEFINED_REF diagnostic locations point to declaration keywords (edge, graph), not to the undefined name itself.
For example, when an edge references an undefined node Foo, the diagnostic's source location points to the edge keyword — not Foo. This means extracting the undefined name from the diagnostic's position (what the cursor is over) would fail in most cases.
The solution: extract the name from the diagnostic message first (regex /'([^']+)'/), falling back to getWordAtPosition() only if the message doesn't contain it. This is more reliable than position-based extraction because the diagnostic message always names the undefined reference.
Process Notes
v3.3 applied two new process improvements from v3.2's retrospective:
- R-PROC-11 (pre-round tech debt verification): Before scoping a tech debt round, verify each item exists in the codebase. This prevented a wasted round like v3.2-R3 (where TD-05 was already implemented).
- R-PROC-12 (merge single-item rounds): Rounds with fewer than 2 substantive changes merge into adjacent rounds.
Both code actions (R1) and conditional routing (R2) were classified as MEDIUM tier with A2+A3 debate. Document symbols and condition type validation (R3) were DIRECT. All 4 rounds passed first try.
Try It
npm install -g @graft-lang/graft@3.3.0
# Compile
graft compile pipeline.gft
# Run with conditional routing
graft run pipeline.gft --input '{"query": "test"}' --dry-run
# Check (parse + analyze, includes condition type validation)
graft check pipeline.gft
Stats
| Metric | v3.2 | v3.3 | Delta | |--------|------|------|-------| | Tests | 582 | 636 | +54 | | Ratchet decisions | 180 | 190 | +10 | | Agent calls | ~9 | ~12 | +3 | | Rounds | 4 | 4 | 0 | | NEEDS_CHANGES | 0 | 0 | 0 | | Debated rounds | 1 | 2 | +1 | | Direct rounds | 2 | 1 | -1 | | New error codes | 0 | 1 | +1 |
Built with Claude Opus 4.6 via Claude Code. April 2026.