What this is: the introduction to tinycmdr, the agent harness I run on every machine in my fleet, now public at github.com/trappsquid/tinycmdr. One Python file, no framework inside it, and a fixed prompt small enough to prefill on every turn. MIT licensed.

Where to get it: the repository and the latest release. This post is an introduction; the measured surface, the budgets, and the failure handling live in the repo.

No figures on this page, on purpose. Everything countable in this project moves with the next release, so this page points at the build’s own numbers instead of pinning them, and the repository is the only place a number can be current.

I run my own infrastructure: a mail server, a monitored home network, and a workstation that serves local models. For the last stretch of that build I wanted an agent on each machine, one that can read a log, restart a service, and tell me what it did, driven from a chat server I host myself.

The problem was the harness. Every one I tried spent most of its prompt on itself, and the framework I started with burned more than 16,000 tokens on its own scaffolding before the first tool call. On a local endpoint that is prefill you pay again on every uncached turn, and window you no longer have for the task.

tinycmdr’s fixed prompt is a fraction of that, and it stays fixed: the system prompt plus the tool schemas a request actually carries, with the rest of the tool list costing nothing until a session asks for one. The build prints the figure for your own endpoint, and the repository re-measures it against whatever model you point it at.

What it is:

  • One Python file, no package and no framework inside it. You can read the whole agent end to end.
  • A dependency list you can read in one sitting. Nothing in it is a framework.
  • Any OpenAI-compatible endpoint: llama.cpp, vLLM, SGLang, Ollama, or a cloud API as a fallback. A local failure does not fall through to the internet unless you turn that on.
  • State in plain files beside it: sessions, memory, notes, scheduled jobs. No database, no container, no daemon. It is one process on the machine it manages, under whatever supervises it there.

The tools are real ones: shell, Python, file read and edit, content search, page fetch, web search, cron, sub-agents, and a tool the agent writes for itself that is live on its next call. It answers on a browser page (loopback and token-gated by default), in a terminal session, in Mattermost or Telegram, or as a one-shot command from a script.

The half I care about more is the runtime around the model call, because every piece of it exists after something went wrong on a machine I could not see: per-run budgets, a loop guard that refuses a repeat of the same call, a stall watchdog that abandons a dead run and says so, periodic check-ins, mid-run steering, and stop and restart that report their real state.

What it is not: a coding agent, and not a sandbox. It runs as your login user with your rights, and the command blocklist is a regex. Untrusted text that reaches the model can ask it to run something. What a team-maintained harness gives you and this does not is a sandbox, an ecosystem, benchmarks, and a team.

What a run looks like

The transcript in the page is the run you would get in a terminal: the order, each tool call with its arguments, what came back, then the answer, with the elapsed time and token counts on the run header. This one fetched a page whose body carried an instruction aimed at the assistant.

A run that fetched a page containing a prompt injection, with the reply refusing it

A fetched page carrying injected text. The run quoted the block back and refused it. That refusal is the model behaving, not a boundary in the harness, which is the honest way to read it.

The page

The page is the default door: loopback, token-gated, and served by the same process as whichever chat lanes you configure. The rail on the left is every conversation that host has kept, and the same window carries the tasks, jobs, log and inventory. The palette lives in a config file the host owns, so a themed install is one text file away.

The tinycmdr page at its landing state

The landing state, on a laptop. The rail holds every conversation that host has kept.

The tinycmdr mascot

The mascot, drawn for the project and shipped with it. It is the figure standing on the landing page above.

The terminal

The same transcript runs in a terminal. tinycmdr cli keeps the cards in normal scrollback, and tinycmdr --app takes the whole window. Both draw the same cards the page does, so the door you are at changes the furniture and not the transcript.

The full-screen terminal session, with the rail on the left and the run’s cards on the right

The full-screen session. The rail carries the session id, the context fill, what the last run spent, the model, the key list, and the mascot drawn in text; the bottom line is the run summary the page shows too.

The offer at the bottom of that shot is not a command. The run had reached the same calls by hand in eight earlier runs, so the harness asked whether to build them into a tool this machine keeps. Answering yes is the self-extension path, and the tool it writes is live on the next call.

Install

Windows, in PowerShell, no administrator rights:

irm https://github.com/trappsquid/tinycmdr/releases/latest/download/install.ps1 | iex

Linux and macOS:

curl -fsSL https://github.com/trappsquid/tinycmdr/releases/latest/download/install.sh | bash

Every release carries three packages (Windows, Linux, macOS) and a SHA256SUMS file that covers them. The stable download names always point at the newest build, and the archives ship the test suites the release gate runs.

Source, the issue tracker, and the longer writeup: github.com/trappsquid/tinycmdr. What it costs, how it fails, how it compares with the harnesses other people maintain, and the release flow are all in the repository, kept next to the code that decides them.

This runs on machines that also appear in two older posts here: the workstation that serves the models, and the chat server that carries the commands.