A succinct core for agentic research.
MTA keeps the core minimal so that every behaviour is easy to trace, change and study. Planning, memory and context management are built on top, not baked in.
Works with any OpenAI-compatible chat-completions endpoint: vLLM, SGLang, Ollama or a hosted API. Point MTA_BASE_URL at it and go.
Every episode runs in a fresh Docker container that is removed afterwards, so each run starts from the same image and leaves nothing behind.
Each episode is one serializable trajectory: SFT-ready messages, plus per-step slots for token IDs, logprobs and loss masks.
The model sends a tool call, the sandbox returns what happened, and the loop repeats until the model calls submit. Below is a replay of a real run, step by step; every step is recorded in the trajectory.
Each box is a small Python interface: swap the model, the sandbox or the tools without touching the loop. Read the design →
Set MTA_BASE_URL and MTA_MODEL in .env, then hand the agent a task in plain language.
$ uv run mta "Numerically solve the Lorenz system (sigma=10, rho=28, beta=8/3) from (1, 1, 1) over t in [0, 40] with scipy, and write the maximum value of z to /root/zmax.txt with 3 decimals." --image python:3.12-slim
import asyncio from mta import (Agent, DockerEnvironment, OpenAIModel, TrajectoryRecorder, default_tools) from mta.config import load_dotenv, model_config_from_env TASK = ("Numerically solve the Lorenz system (sigma=10, rho=28, beta=8/3) from (1, 1, 1) " "over t in [0, 40] with scipy, and write the maximum value of z to /root/zmax.txt " "with 3 decimals.") async def main(): load_dotenv() model = OpenAIModel(model_config_from_env()) async with DockerEnvironment(image="python:3.12-slim") as env: agent = Agent(model, env, default_tools(), recorder=TrajectoryRecorder("runs/demo.json")) return await agent.run(TASK) traj = asyncio.run(main())
The trajectory is written after every step, so even a crashed or timed-out run keeps everything up to its last step.
{ "task": "Numerically solve the Lorenz system (sigma=10, rho=28, beta=8/3) …", "exit_status": "submitted", // | step_limit | error | cancelled "messages": [ /* 18 OpenAI-format messages: system, user, assistant (+ reasoning, tool calls), tool */ ], "steps": [ …, { "index": 4, "finish_reason": "tool_calls", "latency_s": 2.39, "usage": { "prompt_tokens": 1380, "completion_tokens": 366, "prompt_tokens_details": { "cached_tokens": 1152 }, "completion_tokens_details": { "reasoning_tokens": 102 }, … }, "tool_calls": [{ "name": "bash", "arguments": "{\"command\": \"cd /root && cat > check.py <<'EOF' …\"}", "output": "exit code: 0\nDOP853 nfev= 63539 zmax=47.8408286238 at t=0.3814\n…", "metadata": { "returncode": 0, "timed_out": false }, "duration_s": 21.31 }], // filled when the server returns token-level data (e.g. vLLM); null for this hosted API "prompt_token_ids": null, "completion_token_ids": null, "logprobs": null, "loss_mask": null }, … ] }
Every command, its raw output, exit code and timing, step by step.
{"messages", "tools"} is a standard chat-with-tools example, ready for SFT.
When the server returns token-level data, each step keeps token IDs, logprobs and a loss mask for RL (experimental).
An optional integration runs MTA as a Harbor agent, so it can be evaluated on Terminal-Bench locally or in cloud sandboxes. The core never imports Harbor.
Run Terminal-Bench →