Guide

Advanced Topics

Deeper tooling for verification, IR, sandboxes, and power-user workflows.

Codegen with IR

The final build phase, codegen, drives a coding agent to generate your software. The agent does not work from the spec alone: the verified IR is mounted read-only in its sandbox, and the prompt tells the agent to treat it as authoritative. This page shows what the agent actually sees, and how the rustdis example pushes the idea further by turning IR nodes into an executable property-test suite.

What the codegen agent sees

Each spec's codegen run gets a sandbox with the working directory at /code (writable), the project at /project and specs at /specs (read-only), and the IR at /ir (read-only). The standard prompt template (codegen/auto) sets the ground rules, and a per-spec preamble points the agent at the checked model:

Your current working directory is /code and it is writable: write all
generated files under /code.

The project repository is mounted read-only at /project, specs at /specs,
and IR at /ir. Read them for context but never attempt to modify them.

Formal constraints for this spec are under /ir/001-proxy/ (ir.yaml and
consistency.smt2). Treat them as authoritative.

Specs build on each other: spec #0002's agent starts from the code that #0001 produced, staged in a cache directory and snapshotted as a file set between steps. On success, the final snapshot is synced cleanly to dirs.code. You can inspect or swap the prompt with overplane prompt show codegen/auto and overplane build --prompt <collection/name>.

Case study: IR-derived property tests

Because the IR is a machine-readable inventory of every claim in your specs, a later spec can consume it. rustdis (a Redis-compatible server written against seven specs) dedicates spec #0006 to exactly this: it instructs the agent to walk the IR of specs #0001 through #0005 and derive a proptest suite from it. The spec is itself a prompt, and its key fragments show the technique:

Turn the formal IR that Overplane raised from specs #0001-#0005 into an
executable property-based test suite. The IR directories are mounted
read-only at /ir/<spec-file>/ (ir.yaml, consistency.smt2); treat them
as the authoritative source of properties. Do not invent properties that
have no IR node, and do not skip IR nodes that are runtime-checkable.

* Traceability is the point. Create tests/ir_properties.rs where every
  property test is named prop_<spec>_<ir_node_id> (e.g.
  prop_0003_no_resurrection) and carries a doc comment quoting the IR
  node's description and assertion.

* Every runtime-checkable IR node id from specs #0001-#0005 appears either
  in tests/ir_properties.rs or in the VERIFICATION.md SKIPPED table:
  no third bucket.

The generated suite delivers that traceability. Here is the property derived from the #0003 IR node no_resurrection, whose IR description reads "Once a key is unobservable because now >= deadline, it stays unobservable for every later monotonic instant":

/// IR #0003 `no_resurrection` (invariant): "Once a key is unobservable
/// because now >= deadline, it stays unobservable for every later monotonic
/// instant ... No read may observe the old value after expiry."
#[test]
fn prop_0003_no_resurrection(
    ttl in 1u64..10_000,
    advances in prop::collection::vec(1u64..5_000, 1..8),
) {
    let mut s = manual_store(); // virtual clock: expiry is deterministic
    real_reply(&mut s, &[b"SET".to_vec(), b"k".to_vec(), b"v".to_vec(),
                         b"PX".to_vec(), ttl.to_string().into_bytes()]);
    s.advance(Duration::from_millis(ttl)); // reach the deadline
    for a in advances {
        let got = real_reply(&mut s, &[b"GET".to_vec(), b"k".to_vec()]);
        prop_assert_eq!(&got, &RespValue::NullBulk,
                        "expired key must never resurrect");
        s.advance(Duration::from_millis(a));
    }
}

A second example, from the #0004 collections IR, checks the invariant that an empty collection is never observable (draining a list or set deletes the key):

/// IR #0004 `empty_never_observable` (invariant): "An empty collection is
/// never observable ... Deleting the last element deletes the key."
#[test]
fn prop_0004_empty_never_observable(elems in prop::collection::vec(arb_val(), 1..6)) {
    let mut s = Storage::new();
    let mut push = vec![b"RPUSH".to_vec(), b"l".to_vec()];
    push.extend(elems.iter().cloned());
    real_reply(&mut s, &push);
    real_reply(&mut s, &[b"LPOP".to_vec(), b"l".to_vec(),
                         (elems.len() as i64).to_string().into_bytes()]);
    prop_assert_eq!(real_reply(&mut s, &[b"EXISTS".to_vec(), b"l".to_vec()]),
                    RespValue::Integer(0));
    prop_assert_eq!(real_reply(&mut s, &[b"TYPE".to_vec(), b"l".to_vec()]),
                    RespValue::SimpleString("none".to_string()));
}

Beyond single-node properties, the suite includes model-based testing: random command sequences run against both the real store and a naive BTreeMap reference model, asserting identical replies. The result is a verification chain with three links, each mechanical and auditable: spec sentence to IR node (the rationale field), IR node to SMT assertion (the :named labels), and IR node to property test (the prop_<spec>_<node> naming convention).

Caching and cost

Codegen steps are cached like raise steps, with one addition: the cache key includes a content hash of the accumulated output from all prior specs. Edit spec #0003 and rebuild, and specs #0001 and #0002 restore instantly from cache while #0003 onward re-run. Failed agent runs are never cached, so a retry always re-executes. Recorded alongside each cached step are the original run's token usage and cost, which is how the example walkthroughs report their build spend without re-running anything.

Next step Advanced Topics · 4 of 5 Sandbox Run ad-hoc commands in your project's container image with sandbox run.