← Blog

Graft v3.1: LSP Completions, API Surface, and 12 Rounds Without a Bug

4 min read
graftcompilerlspadversarial-debateai-agentstypescript

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.1 is a polish release: no new syntax, no breaking changes. LSP completions, a programmatic API, two bug fixes, and four tech debt cleanups — all in 5 rounds with ~12 agent calls.

What's New

LSP Completion Provider

The biggest feature: autocomplete in .gft files. The completion provider detects 8 cursor contexts and offers appropriate completions:

| Cursor position | Completions | |----------------|-------------| | Top-level empty line | context, node, memory, graph, edge, import (with snippets) | | Inside reads: [ | Context names, produces names, memory names | | After Name. | Fields of that context, produces, or memory | | Inside writes: [ | Memory names only | | After model: | sonnet, opus, haiku | | After on_failure: | retry, fallback, skip, abort, retry_then_fallback | | Inside fallback( | Node names | | Inside graph { } | Node names + done |

The implementation handles block comments (/* */), line comments (//), string literals, and CRLF line endings. All context detection uses regex on the text before the cursor — no partial parsing needed.

node Analyzer(model: sonnet, budget: 5k/2k) {
  reads: [TaskSpec.|]    # ← completes: title, priority
  on_failure: |          # ← completes: retry, fallback, skip, abort
  produces Analysis {
    findings: List<String>
  }
}

Programmatic API

Library consumers can now import from clean sub-paths instead of reaching into dist/:

import { compileToProgram, compile } from '@graft-lang/graft/compiler';
import { Executor } from '@graft-lang/graft/runtime';
import type { Program, ProgramIndex, GraftErrorCode } from '@graft-lang/graft/types';

const result = compileToProgram(source, 'pipeline.gft');
if (result.program) {
  console.log(`${result.program.nodes.length} nodes parsed`);
}

Bug Fixes

Parallel failure strategy bypass (TD-04). Parallel branches were calling ctx.executeNode() directly, silently ignoring on_failure strategies. Now all parallel targets go through executeWithFailureStrategy().

Fallback cycle detection (E-05). on_failure: fallback(self) or mutual fallback loops (A→B→A) now produce SCOPE_FALLBACK_CYCLE at compile time instead of infinite recursion at runtime. Detection uses DFS with an in-stack set, matching the import circular detection pattern from v2.0.

Tech Debt Cleanup

Four mechanical cleanups:

  • Executor/ScopeChecker deduplication: removed nodeMap, edgeMap, contextNames, nodeNames, memoryNames duplicate fields. Both classes now derive from ProgramIndex maps exclusively.
  • CLI formatting: extracted formatTokenReport() from duplicate code in compile and check commands.
  • Foreach error message: removed stale "v1.1" version reference.

The Debate That (Almost) Wasn't Needed

v3.1 ran 5 rounds. Only R1 (LSP completions) used debate — two agents (A2-Pragmatist and A3-Skeptic) with the new adversarial checklist. R2-R4 were DIRECT (no debate). R5 was TEST-ONLY.

A3-Skeptic's analysis identified 10 potential issues, 3 of which directly shaped the implementation:

  1. Block comment detection: A2 only handled // line comments. A3 correctly identified that /* */ needs a full scan from document start tracking block comment state.
  2. CRLF line endings: On Windows, TextDocument.getText() may contain \r\n. A3 caught that regex anchors like $ fail with trailing \r.
  3. Missing produces names in reads: The plan said "context/memory names" for reads completions, but the scope checker also accepts produces output names. A3 caught this silent omission.

This is the 12th consecutive round where DIRECT implementation passed first try (v3.0 R5-R8 + v3.1 R2-R4 + more). The merged Step 3+4 (convergence + implementation by the same agent) worked well in R1, eliminating the context loss between steps.

Stats

| Metric | Value | |--------|-------| | Tests | 537 (60 new) | | Ratchet decisions | 172 (12 new) | | Rounds | 5 (1 debated, 3 direct, 1 test-only) | | Agent calls | ~12 | | First-try pass rate | 100% (5/5) | | New source files | 2 (types.ts, format.ts) | | Breaking changes | 0 |

Try It

npm install -g @graft-lang/graft

# Compile a pipeline
graft compile pipeline.gft --out-dir ./output

# Check without generating files
graft check pipeline.gft

# Run with dry-run mode
graft run pipeline.gft --input '{"task": "review"}' --dry-run

Source: github.com/JSLEEKR/graft