Skip to content

RFC: headless agent experiment lab (@maka/lab) #31

Description

@Astro-Han

Motivation

Today we tune agents by feel — swap a model, tweak a prompt, eyeball the result. As agents get more complex (multi-tool, multi-step), that hits a ceiling fast. We want a way to treat an agent configuration as something testable, measurable, and comparable — run a config against a task, record exactly what it did, score it, and compare configs head-to-head.

Most of the foundation already exists. @maka/runtime is Electron-free and headless-capable (RuntimeRunner takes an InvocationRequestInvocationResult), and runtime-events.jsonl is already a canonical execution trajectory. What's missing is (1) a headless process entry point and (2) the experiment layer on top.

Goal & non-goals

MVP = the shared engine: Config × Task → run in sandbox → trajectory → score → compare. First task type: coding tasks scored by running tests (SWE-bench style). This engine is the model-comparison feature for free.

Explicitly out of MVP scope (all pure additions later, no debt): self-evolution / optimization loop · LLM / rule evaluators (MVP = command/test evaluator only) · Docker backend & real benchmark task packs · Inspect AI adapter · network allowlist · any Electron UI · a general-purpose third-party headless entry point.

Design: three orthogonal contracts

The core idea is experiment = Config × Task, so the two must be separable. Three contracts in @maka/core:

  • Taskinstruction + initial workspace + verification (test command + FAIL_TO_PASS/PASS_TO_PASS semantics). Fields mirror the SWE-bench instance schema so a SWE-bench instance maps in losslessly. Verification lives on the Task, not the Config — so a config under test cannot grade itself (essential for honest benchmarking).
  • Config (the variable under test) — does not invent a new format; references Maka's existing model/connection selection, toolset, and params, plus a systemPrompt override. The "toolset" is a capability set, not an interactive permission policy.
  • Execution — in the MVP this is not a config field, it's a single non-overridable safe default. It graduates into a contract later (a pure addition) when a Docker backend lands.

Plus ResultRecord (one per run: pass/fail + cost / duration / steps).

Safety model

The lab is a separate trust context, physically isolated from the interactive permission model. Maka's interactive ask/permission flow does not enter the lab — so "non-interactive batch" and Maka's "never auto-approve" stance don't conflict; they're two different trust models, not a relaxation of the existing one.

Inside the lab, tools auto-approve, and safety comes from isolation, not asking. The non-overridable safe default per run:

  • a throwaway workspace copy (never reuse an agent-mutated dir)
  • network off by default (MVP fixtures pre-vendor deps; a network allowlist is a named later addition)
  • env allowlist (no host secrets)
  • timeout + process-tree kill + output cap + temp cleanup
  • evaluator runs separately after the agent finishes, using hidden / re-injected test assets (so the agent can't game the tests)

Packaging & output

  • New package @maka/lab (depends on core + runtime + storage) for orchestration + scoring + compare.
  • A thin CLI bin with experiment-only verbs (run, compare). Peer to desktop, no mutual dependency. (@maka/lab ≠ "headless" — headless is a property of @maka/runtime; the lab is its first consumer. A general third-party headless entry point would be a separate layer.)
  • Reuses the existing run dir (sessions/<id>/runs/<runId>/) and runtime-events.jsonl.
  • ResultRecord JSONL is canonical truth; MVP derives a markdown comparison table (git-diffable, shareable) + a terminal table. HTML/UI are later derived views that never touch the data.

End-to-end: maka-lab run → read Task + a set of Configs → each combo runs once in the safe sandbox (via RuntimeRunner) → trajectory to runtime-events.jsonl → evaluator produces pass/fail + metrics → write ResultRecord → derive markdown table.

Proposed first step

A walking skeleton: single Config × Task end-to-end on one hand-built fixture — the three contracts (minimal), the safe sandbox default, one RuntimeRunner run, the command evaluator, one ResultRecord. No matrix / compare / CLI polish yet. The contracts are easy; the integration (sandbox + headless RuntimeRunner + evaluator composing into one real run) is where the surprises hide, so de-risk that first.

Open questions

  1. Comparison axes for the first experiments — model × systemPrompt enough, or also vary toolset/params early?
  2. Network-off-by-default for MVP fixtures — acceptable, or is a minimal allowlist needed sooner?
  3. Does mirroring the SWE-bench instance schema on Task match how we'd want to ingest real benchmark packs later?

Prior art surveyed: jeju · SWE-bench · Inspect AI

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions