Installation
Public preview
Stoat 0.5.0 is the first public preview, released beside CritterWatch 1.1; the number matches CritterWatch's first BSL release rather than Stoat's maturity. The coordination model, the MCP tools and the UI are real and in daily use, but the surface can still change before 1.0, which follows the .NET 11 release train. Stoat is free on its own machine, and the licence a server for other machines needs is not checked yet (see Licence). Tell us what breaks: https://github.com/JasperFx/stoat/issues.
Requirements
| .NET | 10.0 |
| A database | None to install. The store is Fisher on a SQLite file, created on first run. |
| A licence | None on its own machine. A Stoat other machines reach needs a CritterStack licence: see Licence. |
Nothing else is required. Stoat does not need your solution to reference anything. Runs reach it as HTTP posts from Bobcat's publisher, so it needs no Bobcat package either. For GitHub issue and PR nodes, Stoat needs a GitHub token: GITHUB_TOKEN, GH_TOKEN, or a signed-in gh CLI (see Configuration).
Install
Stoat ships as one package: a .NET global tool. Two commands install it and wire a repository:
dotnet tool install -g Stoat
stoat setupIf this is the first .NET global tool on the machine, dotnet prints a line adding ~/.dotnet/tools to your PATH: run it, or open a new terminal, before stoat setup.
Then start the server in its own terminal:
stoatstoat setup is run from your repository. It writes the Stoat MCP server into the repository's .mcp.json, installs the session hooks into Claude Code's ~/.claude/settings.json, checks whether a Stoat server answers, and says what licence applies and what to do next. Restart Claude Code in that repository afterwards so it reads both files, and approve the stoat MCP server when it asks.
Commit .mcp.json. It gives everyone who clones the repository the Stoat server, and until it is committed, a gate run in that checkout reads "with uncommitted changes in the tree" and proves no commit.
The tool is a web application. Starting it binds two ports and serves the web UI, the API and the MCP endpoint from one process:
Coordination http://localhost:5526
Run receiver http://localhost:5527
MCP http://localhost:5526/api/mcpOpen http://localhost:5526 for the ready board, the plans, the agent roster, repositories and memories. The run board and the Event Model canvas are Bobcat's: bobcat ui, on http://localhost:5525, from the free bobcat tool (dotnet tool install -g Bobcat.Console).
Why not 5525
5525 is the address every Bobcat publisher probes by default, and it belongs to bobcat ui, the run board and Event Model canvas in Bobcat's free tool. Stoat receives runs on its own port, and a run reaches both because BOBCAT_MONITOR_URL is a list. See Ports and wire contracts.
What stoat setup writes
$ stoat setup
Stoat setup 0.5.0 (preview)
Changed /Users/you/.claude/settings.json
+ Notification hook: session sidecar: an agent blocked on a person
+ SessionEnd hook: session sidecar: how a session ended
+ SessionStart hook: session journal: why a session started
+ SessionEnd hook: session journal: why a session ended
+ PostToolUse (Bash) hook: session journal: commits and pull requests
Changed /Users/you/code/my-repo/.mcp.json
+ mcpServers.stoat -> http://localhost:5526/api/mcp
Backed up the previous file to /Users/you/.claude/stoat/backups/settings.json.20261025-120000
Recorded what was written in /Users/you/.claude/stoat/setup.json, so setup can upgrade it and --uninstall can take it back.
Server http://localhost:5526 answering
Receiver http://localhost:5527 answering
Licence not needed on this machine: Stoat is free where it runs, for every plan, agent and MCP tool.
A Stoat that other machines reach is Stoat Team, which needs a CritterStack licence: https://jasperfx.net/our-products/#critterwatch-plans
The server has no licence key..mcp.json | One stoat entry under mcpServers, merged into whatever servers the file already names. The file is created if the repository has none. |
settings.json | Five hooks, merged into the hooks already there: the two sidecars (Notification, SessionEnd) that tell the agent roster when an agent is blocked on a person and how a session ended, and the three journal hooks (SessionStart, SessionEnd, Bash PostToolUse) that record why a session started and ended and what it committed and opened. They are exactly the hooks in the reference below. |
| A backup | Each file it changes is copied to ~/.claude/stoat/backups/ first. |
| A record | ~/.claude/stoat/setup.json lists what setup wrote, so it can change it later and take it back. |
It is built to be re-run:
- Re-running is safe. Nothing that is already in place is written again, so a second run changes nothing and backs nothing up.
- It owns only what it wrote. A hook or server you wrote by hand, even one identical to setup's, is left alone and never removed. So is an entry setup wrote that you have since edited.
- Upgrades change only setup's own entries. After a new version changes a hook, run
stoat setupagain. It replaces the hooks it recorded and leaves the rest. - It refuses rather than guesses. A file with comments (rewriting it would lose them), a file that does not parse, or a
stoatserver that setup did not write: each is reported and left exactly as it was.
| Flag | |
|---|---|
--dry-run | Print what would change, and write nothing |
--uninstall | Remove exactly what setup added: its hooks, and its stoat entry in every .mcp.json it wrote to. A file setup created and that is empty again is deleted |
--repo <folder> | The repository to wire, when not the git root above the current folder |
--no-repo | Wire the machine's hooks only |
--claude-dir <folder> | Claude Code's config folder, when not CLAUDE_CONFIG_DIR or ~/.claude |
--url <url> | Stoat's address, when it is not http://localhost:5526. The MCP entry and the hooks point at it |
--receiver-url <url> | Stoat's run receiver, for the server check, when it is not STOAT_RUN_RECEIVER_URL or http://localhost:5527 |
Setup never starts a server. While none answers, every hook is quiet: each sidecar is a file write, and each journal hook is a curl … || true that fails at once.
Prefer to write the configuration yourself? It is all on An agent's first session.
Where its state lives
~/.stoat/coordination/stoat.db the coordination store (plans, claims, memories, observations)
~/.bobcat/monitor/runs/ the run archive and the Event Model store
~/.claude/stoat/ setup's record and backups, and the hooks' session sidecarsThe first two are overridable: Stoat:ConnectionString or Stoat:DataPath for the first, Monitor:DataPath for the second. See Configuration.
Compacting the store
The server keeps the observed streams short on its own. To give back the space a store already used, stop the server and compact it:
stoat db-compactThat compacts every observed stream (GitHub, feeds, pins, runs, spec evidence, sessions) to its latest state, checks that each one still produces the state the plans read, and runs VACUUM to give the space back. Plans, claims, memories and repositories are never compacted: their history is the record. It refuses to run while a Stoat server answers at --url (default http://localhost:5526).
| Flag | |
|---|---|
--keep <n> | Keep the newest n events on each observed stream (default 0) |
--batch <n> | Events replaced per step (default 25). Keep it small: each step loads that many |
--no-vacuum | Skip the VACUUM |
--adopt-rebuilt | Where a stream no longer produces its stored state, store what it produces instead |
--url <url> | The Stoat server to check for before starting |
If the check finds a stream whose stored state does not match what the stream produces, it names the stream and stops before VACUUM. That mismatch was already there, not caused by the compaction. --adopt-rebuilt stores what the stream produces, which is what a rebuild would do anyway.
Licence
Stoat is free on its own machine. The stoat tool serving the computer it runs on needs no licence, for every plan, agent and MCP tool, and that includes production use. A Stoat that other machines reach, so that a team's agents coordinate through one server, is Stoat Team, and that needs a CritterStack licence. Any CritterStack licence works, because Stoat and CritterWatch are sold as a suite: a JASPERFX__LICENSEKEY you already have for CritterWatch works here too.
The line is where a request comes from. A request is local when it comes from a loopback address (127.0.0.1, ::1) and carries no X-Forwarded-For or Forwarded header. That covers everything the tool's own users do: the web UI in a browser on the same machine, Claude Code's MCP client and hooks, stoat run-plan, stoat gate exec and stoat gate run. Stoat binds localhost by default, so serving other machines is always a deliberate step: binding a wider address in ASPNETCORE_URLS, or putting a reverse proxy in front.
Not checked for other machines in this preview
0.5.0 does not check the licence at all: a request from another machine is served as if licensed. A later release turns the check on for other machines; your own machine stays free.
To license a Stoat Team server, get a licence at https://jasperfx.net/our-products/#critterwatch-plans and set it where the server starts:
export JASPERFX__LICENSEKEY="<your key>"
stoator set JasperFx:LicenseKey in configuration. The key is read once at startup, so restart the server after changing it. A server running in the Development environment is licensed with no key, as CritterWatch's console is.
The web UI's ⓘ button opens About Stoat: the version, whether this browser is on the server's own machine (free) or another one, who the server's licence is for and when it expires, and links to these docs, the release notes and the licence page. GET /api/license and GET /api/about answer the same, and stoat setup says it too.
Once the check is on, from another machine
What a caller on another machine will get from a server with no active licence, once a release enforces the check. Callers on the server's own machine are never refused.
| Still works | Refused |
|---|---|
The Bobcat run wire: runs posted to the receiver (/api/ingest), GET /api/runs, the Event Model wire, and the runner wire a resident Bobcat runner calls | Every route under /api/plans, /api/agents, /api/repositories, /api/memories and /api/runners, with HTTP 402, reads included |
GET /api/license and GET /api/about, which is how the UI and stoat setup find out | Every one of the 71 MCP tools, which answer license_required |
The UI shows an Unlicensed or License expired badge, and its coordination views say why they are empty. An MCP tool answers with a readable refusal rather than a transport error:
{"error":"license_required","message":"Stoat is free on its own machine; a Stoat that other machines reach is Stoat Team, which requires a CritterStack license (https://jasperfx.net/our-products/#critterwatch-plans). …"}