tomo-labs

module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: MIT

README

tomo-labs

ci Go Reference Go Report Card

tomo-labs puts coding agents through the same tasks on the same model and measures what actually happened, not what a leaderboard says happened. Every agent runs in its own throwaway container, every request and response it sends is captured verbatim, and every result is graded from the files it left on disk, not from what it claims to have done.

InstallQuick startResultsScenariosAdding a toolDocs

Agent benchmarks usually compare one thing everybody actually cares about (did it get the task done) by changing three things at once: the model, the prompt scaffolding, and the tool's own overhead. That is not a comparison, it is three experiments wearing one number. tomo-labs holds the model fixed. A trace proxy sits in front of every agent and forwards every request to the same free model with the same deterministic decoding settings, whatever wire dialect the agent's SDK speaks, OpenAI chat, Anthropic Messages, OpenAI Responses, or Gemini's API. What is left to differ is the agent: how many turns it needs, how many tokens it burns getting there, how much memory it holds, how big its install is. Eight agents run through the same harness today: tomo, codex, opencode, claude-code, openclaw, hermes, gemini-cli, and pi. Adding one more is a Dockerfile and a small adapter script, not a fork of the harness.

Install

git clone https://github.com/tamnd/tomo-labs
cd tomo-labs
go build -o bin/lab ./cmd/lab

Or run it straight from source with go run ./cmd/lab ..., which every example below uses.

You'll need:

  • Go 1.26.5

  • podman or docker (the harness detects which is present; set LAB_RUNTIME to force one)

  • a key for an OpenAI-compatible endpoint. The default targets the OpenCode Zen free tier, whose deepseek model does tool calling:

    export OPENCODE_API_KEY=...
    

Quick start

go run ./cmd/lab build            # base, proxy, and every wired tool image
go run ./cmd/lab run tomo         # run tomo through every scenario
go run ./cmd/lab report           # summarize every captured run as a table

That's the whole loop: build the images once, run whichever agent you want against whichever scenario you want, then read the report. A few more useful shapes:

go run ./cmd/lab run tomo 03-bugfix-fizzbuzz     # just one scenario
go run ./cmd/lab -p "explain this repo in one line"   # one ad-hoc prompt, every
                                                        # tool, in parallel
go run ./cmd/lab meta                             # capture each tool's version
                                                    # and release date
go run ./cmd/lab report --json                    # the same summary as JSON
go run ./cmd/lab tools                            # list wired tools
go run ./cmd/lab scenarios                        # list scenarios

Two things keep a run from swinging on the model's luck, and neither is tuned to any one scenario. The proxy forces greedy decoding (temperature 0, top_p 1, a fixed seed) onto every completion request, so a repeat run sees the same sampling. On top of that the harness gives each scenario up to LAB_ATTEMPTS tries (default 3) and stops at the first pass, absorbing the run-to-run nondeterminism a hosted model still shows even under greedy decoding. result.json records how many tries a pass took, so flakiness stays visible instead of hidden.

Runs go through a worker pool, LAB_CONCURRENCY deep (default 3), each with its own proxy container and port, so a full sweep is bounded by the slowest few runs rather than the sum of all of them.

What a run leaves behind

Every run writes under $HOME/data/<tool>/<scenario>/<timestamp>/:

attempt-N/
  work/            the tree the agent worked in, exactly as it left it
  trace/
    config.yaml      the config the tool ran with
    requests.jsonl    one line per model request, body included, key redacted
    resp-N.txt        the raw response for request N, streamed or not
    usage.jsonl       token usage per response
    latency.jsonl     per-call time to first byte and total
    stdout.log        what the tool printed
    time.txt          GNU time report, including peak memory
result.json        the scored summary: passed, attempts, tokens, rss,
                   latency, wall time, disk, install footprint

Nothing is summarized away. If a number in the report table looks wrong, the request that produced it is sitting right there in plain text.

Results

Eight tools against the same free deepseek model through the same trace proxy, so what differs below is the tool, not the model. lab report splits them by how they work, the tools that lay out a plan or spawn a subagent apart from the ones that run a single flat loop, and keeps only the latest run of each scenario, so a tool's row is its current state over the same 14 scenarios, not a history that still counts runs it failed before an adapter bug got fixed. That is what makes the columns comparable: pass reads as N of the 14 scenarios, plans is how many of those it chose to plan on, and tokens is the total across all 14, so a bigger number means more work spent, not more runs recorded. Both tables are ordered by tokens. Cost prices those tokens at DeepSeek's published paid rates (the runs themselves were free), which is the dollar figure the token gap becomes once you leave the free tier.

Tools that plan:

tool version pass plans tokens cost install
tomo v0.2.4 14/14 4/14 187,404 $0.027 21MB
opencode 1.17.18 12/14 2/14 457,807 $0.052 420MB
codex 0.144.1 14/14 3/14 732,370 $0.066 423MB
openclaw 2026.6.11 14/14 1/14 1,095,701 $0.114 407MB
hermes 0.18.2 14/14 3/14 1,168,925 $0.106 221MB
claude-code 2.1.207 14/14 3/14 1,793,716 $0.150 322MB

Tools that run flat:

tool version pass plans tokens cost install
gemini-cli 0.50.0 5/14 0/14 112,988 $0.011 181MB
pi 0.80.6 14/14 0/14 244,455 $0.033 156MB

Every version above is that tool's latest published release as of the run, checked against its npm or module registry directly, not a stale pin. lab meta captures the version and its release date after every build so the table never drifts from what actually ran; run lab report yourself for the full columns (release dates, cache hit rate, average tokens, RSS, ttfb, wall time).

A few of these deserve a note.

Token use is the headline, and cost is the same story in dollars. Among the tools that plan, tomo does all 14 tasks in a fraction of the tokens: 187k total against 732k for codex, 1.10M for openclaw, and 1.79M for claude-code, which on the paid tier is under 3 cents against 7, 11, and 15. It plans in context, updating one checklist in the same turn, rather than re-reading its own state in a fresh context per step. pi spends more than tomo but still runs lean; gemini-cli spends the fewest tokens of all, but it does not plan at all and drops 9 of the 14 scenarios, so its cheapness is mostly work it never finished.

Planning is a choice a tool makes per scenario, not a fixed capability, which is what the plans column shows: even the planners lay out a plan on only a few of the 14 tasks and run the rest flat. openclaw is the clearest case, it carries a plan tool and a whole subagent layer but planned just 1 of 14 until the prompt asked, in plain terms, for a live plan it kept current as it worked. The split into two tables is by whether a tool ever plans, tomo and the others do on at least one scenario, pi and gemini-cli never.

Install footprint, not image size, is the honest size axis. Image size is dominated by the shared base every tool sits on (Python, Node, a Go toolchain), so it says more about the base than the tool. The install layer is the tool's own bytes on top of that base: 21MB for tomo's single static binary against 150 to 420MB for a Node dependency tree.

Time to first byte is left out of the tables on purpose. It is bounded by the hosted model, the same upstream for every tool, so it clusters in the same couple of seconds for everyone and is not a real axis of difference here; lab report still prints it if you want to see for yourself.

gemini-cli's 5/14 is mostly the model missing a step, not a wiring bug: it makes only 2 to 3 requests per scenario, so it rarely retries the way the others do, and it drops the multi-step scenarios where a plan would have kept it on track. Its wire translator works end to end. pi is the opposite kind of flat, a minimal harness that runs the whole task in one loop and passed every scenario cleanly. pi does ship a plan mode, but it is a read-only exploration extension gated behind an interactive prompt: it writes a prose plan and asks, in the TUI, whether to execute, rather than exposing a plan tool the model calls mid-run. In a one-shot headless run there is no prompt to answer and no plan tool to record, so pi stays flat here by design, not for lack of trying.

The 00-hello scenario is a baseline, just the prompt Hi!, isolating the fixed round-trip cost every tool pays before it does any real work. See the Hi! baseline results in tomo's own README for that table; it lives there since it's the number tomo's README leads with.

The scenarios

Ordinary tasks a capable agent should handle, each with a checker that grades the result on disk rather than on what the model said, plus the 00-hello baseline above:

id task
00-hello say hi, no task beyond completing the round trip
01-file-organize sort a flat pile of files into folders by extension
02-json-transform filter and sort a JSON array of users
03-bugfix-fizzbuzz fix a FizzBuzz that never prints FizzBuzz
04-web-extract fetch a page and name the cheapest product
05-log-count count HTTP 500s in an access log
06-codegen-primes write, build, and run a Go primes program
07-refactor-dedupe remove a duplicated function, keep the test green
08-data-summary total a sales CSV and find the top day
09-project-scaffold scaffold a small project and run its make target
10-reasoning-calc follow a precise two-step calculation into a file
11-storefront-budget fetch a page, read a local budget, and write the affordable products in order
12-invoice-join fetch a catalog, join it with a local orders CSV, and total the invoice
13-release-fix fetch tax rates, fix a bug, build, run, and get the Go test suite green

Adding a tool

See docs/DESIGN.md for the architecture and the trace schema, and tools/openclaw/README.md for the two files a new tool needs: a Dockerfile on top of tomolab-base, and an adapter.sh that points the tool at $LAB_BASE_URL and runs the task in /work. The harness never reads a tool's own code, only these two files, so every tool is on the same footing.

How it works

scenario prompt ─▶ tool container ─▶ trace proxy ─▶ upstream model
   (/scenario)      (runs in /work)   (records +      (deterministic,
                                        translates       same for
                                        the wire)         every tool)
                          │
                          ▼
                     work left in /work ─▶ checker ─▶ result.json

The proxy is the one piece every tool shares. It records every request and response verbatim, forces deterministic decoding, and translates whatever wire the tool's SDK speaks into one chat-completions call upstream, using the translators in tamnd/tomo/pkg/wire. A tool never talks to the real model directly, and never knows the proxy is anything other than the API it expects.

Layout

cmd/lab         the harness CLI
cmd/proxy       the trace proxy binary
pkg/lab         the harness as a library: build, run, report
pkg/proxy       the trace proxy as a library
pkg/container   a typed wrapper over the docker or podman CLI
scenarios/      one directory per task: prompt, fixtures, checker
tools/          one directory per tool: Dockerfile and adapter.sh
docs/           DESIGN.md, the architecture and trace schema in full

Directories

Path Synopsis
cmd
lab command
Command lab is the whole harness from the outside: build the images, run a tool over the scenarios, and report.
Command lab is the whole harness from the outside: build the images, run a tool over the scenarios, and report.
proxy command
Command proxy runs the lab's trace tap.
Command proxy runs the lab's trace tap.
evals
aider/answers/go-bowling
Package bowling implements scoring for the game of bowling.
Package bowling implements scoring for the game of bowling.
pkg
container
Package container is a thin, typed wrapper over the docker or podman CLI.
Package container is a thin, typed wrapper over the docker or podman CLI.
lab
Package lab is the agent-eval harness as a library.
Package lab is the agent-eval harness as a library.
proxy
Package proxy is the lab's trace tap.
Package proxy is the lab's trace tap.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL