Unchained Labs

Unchained Labs — an agentic lab

Two halves of one bet: agent work is a graph, not a chat.

Kymatics is the product — speak an intent, and it becomes a queued agent job that runs in an isolated workspace and streams the build back to you. The five tools are what we built after running enough of those to know where it goes wrong: one lints the graph, one prices it, one measures whether the verification bought anything.

The second half exists because of the first. And when we pointed the tools back at our own stack they found real bugs in the product and two in the security scanner itself.

  • 1 product · 6 tools
  • 351 tests passing
  • CI green on every repo
  • MIT
  • alpha
The whole lab in 76 seconds. Every frame is real CLI output — including the unauthenticated shell endpoint on our own control plane, and the two false cleans the scanner had to be fixed for.

The product

Kymatics — speak an intent, get a built thing

A voice-first pipeline that turns spoken work into queued agent jobs, runs each one in an isolated workspace, and streams the build back live. Three services, all public, all deployed.

flowchart LR
  V(["voice"]) --> L["Lavoix
speech → text"] L --> S["Seal
intent → a card"] S --> O["Otter
card → a queued job"] O --> W(["isolated workspace
agent runs, output streams back"]) W --> S

Otter

The orchestration engine. Queues and schedules prompts, runs each in an isolated workspace, streams events, keeps full job history, and reports token usage and cost per job.

Rust · axum · Postgres · Redis

Lavoix

Speech in and out. Provider-based STT/TTS behind a FastAPI surface, so the voice layer is swappable rather than welded to one vendor.

Python · FastAPI

Seal

The frontend. A voice-first Kanban over Otter's queue — capture a prompt, watch the job move, read the build output as it happens.

React · TypeScript

Otter is why the rest of this org exists. Once you are running agent jobs by the hundred, correctness and cost stop being someone else's problem — and there was nothing to reach for. So we built it.

The tooling · how they fit

Four questions you currently cannot answer

Running many agents in parallel is a capability most people now have. Running them affordably and correctly is a systems problem, and it was mostly unbuilt — nothing priced a workflow before it ran, nothing checked whether verifiers were independent, nothing linted the spec for the handful of mistakes everyone makes.

flowchart LR
  H["workflow-hub
get a graph"] --> G["graphlint
is it correct?"] G --> P["preflight
what will it cost?"] P --> R(["run it"]) R --> L["localflow
what is it doing, what did it spend?"] L --> D["decorrelate
were the verifiers independent?"] L -.->|the graph that ran| G L -.->|measured cache rate| P D -.->|tune the lenses| G A["authsweep
a graph worth running"] --> G

Before you write it

Start from a graph that already works, instead of the shape you happen to reach for first.

Before you merge it

Does it contain the mistakes everyone makes? A barrier that waits for nothing, three verifiers that fail together, a cycle that never converges.

Before you run it

What will this cost, and which stage dominates? Most people size the fan-out and are surprised by verification.

While it runs

Which session is working, which is waiting on you, and which one has quietly spent more than the rest of the week combined?

After you run it

Did the verification you paid for actually buy anything, or did you pay three times for one check?

The family is self-consistent, and that is enforced rather than claimed. authsweep emits a verify graph that graphlint lints clean and preflight prices; every workflow in workflow-hub passes graphlint with zero findings; and localflow holds the same price table as preflight, asserted string for string. CI fails if any of that stops being true.

And the loop now closes. localflow reconstructs the fan-out a session actually performed and hands it back to graphlint, so the linter finally sees graphs that ran rather than only the ones people wrote down — and it measures the cache hit rate preflight could not derive.

The tools

Six, and a brand system

Each one was written to fix something we hit shipping Kymatics. Every video below is real CLI output.

localflow

62 tests · zero runtime deps · reads only your own disk

A Kanban board for the Claude Code sessions already running on your machine. Live lanes, cost priced from real token counts, and the fan-out that actually happened.

There is deliberately no "done" lane: nothing on your disk records whether a session achieved what it was asked, only that it stopped. Getting the cost right took two corrections — usage is re-emitted as a message streams, and a cache write is billed by how long it lives — both checked against the figure the CLI reports for a run it just did.

graphlint

16 rules · 57 tests · SARIF

Static analyzer for agent workflow specs. Catches barrier misuse, correlated verifiers, missing schemas and non-terminating cycles — before a token is spent.

Two front-ends: imperative workflow scripts parsed with a real JS parser, and declarative graph specs. Both compile to one IR, which is why a rule written for either works on the other.

preflight

82 tests · GitHub Action

Prices a workflow before it runs and comments the predicted agent count and dollar cost on the PR that changed it. Dependabot, but for agent spend.

Every assumption is printed and overridable, and the output is a range with assumed counts marked — emitting a point estimate would imply a precision the input does not contain. preflight calibrate replaces the guessed token profiles with medians measured from your own runs, and refuses to invent the one number it cannot measure.

decorrelate

49 tests · verified vs published examples

Measures whether your verifiers are actually independent. Three skeptics that share a model and a prompt are one check at 3× the price — this puts a number on it.

The number is N_eff, via Kish's design effect. The statistics reproduce the canonical Fleiss worked example to three decimals on all three published figures, because a tool whose whole value is a number has to be right about the number.

authsweep

81 tests · 8 routers · zero tokens · SARIF

Finds route handlers with no authorization check. Across JS/TS, Python and Rust — Express, Fastify, Koa, FastAPI, Flask, axum, actix-web, rocket. Deterministic, no network, evidence on every finding.

The prefilter is the point: on the fixtures it drops 16 of 25 routes before any agent runs, including everything under a router-level guard. The paid verify stage is opt-in — it hands you the graph instead of spending on your behalf.

workflow-hub

6 workflows · 20 tests · all lint clean

Six agent workflows worth copying, one per shape. You own the file after it lands.

No runtime dependency, no version to keep in sync, and deliberately no update command — editing the file is the point. A registry that keeps ownership of the code it hands you is a framework.

branding

43 contrast claims verified · v1

The brand system. Chain Teal on Midnight Navy — measured contrast, computed mark geometry, and docs that are tested against the measurement.

That last part exists because the ratios in palette.md were wrong twice while it was being written: estimated by eye, then stated in a table as if measured. A brand doc that asserts a wrong ratio is worse than one that asserts none.

Dogfooding

What happened when we pointed them at our own product

The honest test of internal tooling is whether it survives contact with the thing it was built for. We ran all five against Kymatics and wrote down every result, negatives included — a report where all five turned out to help would only tell you the author wanted them to.

ToolTargetOutcome
authsweepLavoix Found two unauthenticated endpoints doing paid provider work — an upload goes straight to a speech provider that bills per second.
localflowOtter Otter counted 48× fewer input tokens than a cached run actually sends, and reported 12× less than it cost — it read input_tokens and ignored the prompt cache entirely. On an agentic workload the cache is the input. PR open, checked against the provider's own figure.
localflowpreflight A cache write is billed by how long it lives — 2× input for the one-hour tier against 1.25× for five minutes. preflight had one multiplier, so it was a third light on every cached workload. And the cache hit rate its docs called underivable is now measured and written back.
preflightOtter Both directions. preflight models --format otter-env feeds Otter's price list, so cost is quoted from one CI-checked table instead of two — and preflight calibrate reads Otter's measured per-job usage back, replacing the guessed token profiles with what our own runs actually cost. Prices flow out, measurements flow in.
decorrelateOtter evals Nothing to add. Otter's evals already score against an executable oracle, which is exactly what decorrelate would have told it to do.
graphlintOtter jobs Does not apply. An Otter job is a single prompt in one workspace, and a single-prompt job is not a graph.
authsweepOtter 38 routes, 3 high, and no authorization anywhere: two endpoints that accept a command and run it in a workspace, plus a shell over a websocket. All of it invisible until the tool learned to read Rust — which it did because of this exercise.

Four of six help, and the newest one paid for itself twice. Pointing localflow at the same machine that runs everything else found a third-of-the-bill error in preflight and then found Otter ignoring prompt-cache traffic altogether. Both were caught the same way: claude -p --output-format json reports what a run actually cost, which turns a guess into something you can check.

The most valuable result was still not an integration. Pointing a security scanner at our own code found two bugs in the scanner, both of the one class its own threat model calls the worst: a false clean.

Against Lavoix it read Depends(get_service) — dependency injection — as an authorization guard, and reported "every route has an authorization check" over two open billing endpoints. Against Otter it found no routes at all, because it cannot read Rust, and reported that as a pass too.

Neither was reachable from its fixtures, because the fixtures were written by the same person as the rules and inherited the same blind spots. Real code had shapes the author did not think to invent — and fixing the second one surfaced three more defects, including a severity model that could not tell remote code execution from a record insert. The two rows above are the reason you can believe the three ones.

The argument

Six lines the tools enforce mechanically

  1. An edge is data moving, not order. Most "and then" in an agent script carries nothing and is pure wasted latency.
  2. The plan lives in code you own; the judgment lives in the model. Anything a model decides that could have been code is a reliability bug you chose to ship.
  3. Reduce steps are free. Spawning an agent to "combine the results" is flatMap and a Set — deterministic, instant, zero tokens.
  4. Stream by default. A barrier makes everything wait for the slowest node. Use one only for a true cross-set dependency, and write the reason down.
  5. Verification is usually the biggest line item, and it is findings × lenses, not fan-out width. Everyone sizes the wrong term.
  6. N identical verifiers are one verifier counted N times. Vary the lens, vary the model, or use an executable oracle.

Each tool enforces a subset of that in CI, so it stops being advice.

Honestly

What is not true yet

Everything is alpha, and the npm packages are not published. The install lines in the READMEs will not work until they are. Clone and build, or wait.

The heuristics will produce false positives. graphlint's barrier rule misreads barriers that are load-bearing for reasons outside the file; authsweep is explicitly not a taint analyser. Both document this, and both let you turn a rule off rather than work around it.

preflight's defaults are generic until you calibrate. Uncalibrated, trust the shape — which stage dominates — far more than the absolute figure. preflight calibrate fixes the token counts from your own usage rows, but it cannot recover a cache hit rate, and that is the assumption the total is most sensitive to. It stays a declared guess rather than a fabricated measurement.

authsweep reads JS/TS, Python and Rust. Go, Ruby, Java, PHP and C# are invisible to it. It says so loudly instead of exiting green on an unscanned codebase — but a warning is not coverage. The Rust front-end is also token-structural rather than parser-based: a router composed across files keeps its own path and loses the prefix applied elsewhere.