Shoal owns every layer, from the inference server to the agent loop to
the model itself. What runs where is your call, so nothing in the chain
gets repriced, deprecated, or changed by somebody who is not you.
Machinery
Shoal enforces its own rules
The three rules here are not a list, they are a loop. Hooks fire at
prompt build, during the turn, and at completion, so each turn opens on
the window the last one cleaned. None of them runs at prompt build,
because that is where the loop returns.
01
Chart
Built
During the turn, by tool call
Discovery without reading
A YAML adjacency graph of your codebase. Nodes are files with purpose lines lifted verbatim from their own leading comment, edges are imports, resolved for Go today, plus decomposition lineage and hand annotations. Discovery walks breadth-first from task-seeded nodes with fidelity decaying by distance, under a token budget.
Served by a tool call and never injected into a prompt. It appends at the tail of history, so it never moves a byte the server has already cached.
A map is only worth having while the code still has a shape worth mapping.So the write itself is measured on the way through, before the bytes settle.The same write path updates the chart on its way out, and every candidate is stat'd again at pack time, so correctness never depends on the hook having fired.
02
Bosun
Built
During the turn, on the write path
Structural enforcement
Every write, edit, and patch is measured on the way through. At 500 lines the file is noted. At 600 a decomposition debt opens, and the turn cannot end still owing it without being pressed once. At 700 a write that grows the file past the limit is refused and the previous content restored. The rule forbids compressing logic to duck the limit. Files decompose into domain-cohesive children and the parent keeps orchestration.
The hooks attach inside the registry constructor, so a delegated sub-agent inherits them and cannot write its way out.
Bosun keeps two decks. The repo is the first.The second is the context window, and nothing there can be tidied while the turn is still reading it.
03
Janitor
Built
After the turn, in the idle window
Context hygiene
Cleanup runs outside the main loop and makes no model call at all today, so it cannot touch the KV cache it exists to protect. A pass whose mooring would share the main loop’s berth is refused outright, so a future model-backed janitor already has its guardrail. It reads history without writing to it and stages edits that apply at exactly one cache boundary, the next prompt build, refusing any stage whose prefix moved underneath it.
Capacity-aware scheduling is not wired up yet. The berth ledger can answer the question, but nothing asks it, so the only window today is the idle time after a turn.
It applies nothing itself.The staged edits take effect at the next prompt build, so the following turn opens on a cleaned window and asks the chart again.
The three phases close on themselves. The janitor's staged edits leave
the idle window and take effect at the next prompt build, so the loop
returns to the chart with a cleaned window.
The next prompt build
1ChartDuring the turn, by tool call
2BosunDuring the turn, on the write path
3JanitorAfter the turn, in the idle window
Nothing here is a step in a pipeline. Every phase hands the next turn a
better starting state than the last turn had.
Ontology
The architecture runs on a fixed vocabulary of five words
Nothing enters the vocabulary without passing the admission gate below.
Each word exists because the one above it had to hand the work
somewhere. Open any card to watch the word in use.
01
Shoal
The product
One agent, one binary, one name on the tin. Not a platform hosting several tools.
DemoA session from launch to the first applied edit, start to finish.
One name on the tin still has to run something.Underneath it there is a single engine holding the agent loop, the tools, and the machinery that manages the window.
02
Keel
The engine inside
Agent loop, context machinery, tools. The internal name power users know. Everyone else says Shoal.
DemoThe agent loop running, with tool dispatch, a sub-agent, and the context meter moving.
The engine brings no model of its own, so you bring one.Bring your own model (BYOM) and it runs on this box. Bring your own key (BYOK) and it runs on an endpoint you name.
03
Dock
Where models tie up
An inference engine or endpoint. The llama.cpp dock, an OpenAI-compatible endpoint you run, an Anthropic dock offshore.
Demoshoal up spawning an inference server, then the same session against a remote endpoint.
A dock is not one place, it is places.Room has to be counted before anything loads, so the dock is divided into spots that can be measured.Some of those spots are not on your machine. An offshore mooring is somebody else's hardware, reached with an API key you supply, against a bill you pay. Shoal never finds one on its own and never falls back to one.
04
Berth
One spot at a dock
Holds exactly one moored model, never two. Allocated to fit, and memory-accounted before the model loads. What the ledger cannot measure it admits anyway, and says why.
DemoThe berth ledger sizing a model and refusing one that will not fit, with its alternative.
A spot is only a spot until something is in it.Tie a model up, give it a name and a purpose, and you have the thing you talk to.
05
Mooring
A model, tied up and ready
The dock is the place and the mooring is the ship. It is also the named config record, listing model, dock, and purpose. Several moorings run at once. An offshore mooring is reachable only through a record you wrote. Nothing detects it and nothing falls back to it.
Demo/moor swapping the active model mid-session and naming the cache boundary out loud.
The admission gate
A word gets in only when the tokens it saves exceed the tokens it
costs, a test that can be measured rather than argued. The same gate
sets the
posture toward the cloud. Local-first is a guarantee about defaults
and about who decides, not a ban, so an offshore mooring is part of
the vocabulary. A box on the LAN, a rented endpoint, a frontier API
are all first class, all your call, and none of them assumed.
Surfaces
Every surface is a thin client on the same engine
The engine speaks one typed event stream out and reads one op channel
in. Every event carries its session, turn, sequence, and depth, tool
calls arrive as triads keyed by call id, stop reasons are typed, and
usage is reported per call. No client reaches into the engine, so no
client can drift from what the engine actually did.
Inline TUI
The terminal
Finished output is committed to your terminal's native scrollback, where it scrolls, searches, and copies like anything else you ran. Text streams at line boundaries, a typed diff precedes every write, and Enter steers the agent mid-turn. The lighthouse beam runs on the live status line.
The shared web client opens as an app-mode window over a loopback SSE bridge. Ring replay lets a reconnect catch up on what it missed, and the bridge shuts itself down once it sits idle. Same client code as the extension, same events, same cards.
The extension spawns shoal proto from the binary you already installed and hosts the shared client in a webview, in plain JavaScript with no bundler. Ask About Selection is in the context menu, so you can ask about the exact code you have highlighted. Packaged with vsce.
Nobody has published a controlled map ablation on local models, so the
method here was fixed in writing before the run, and the results could
not be fitted to afterwards. The corpus is the Shoal repository itself,
469 charted files. The ten tasks were all written down before the packer
existed, so none of them could be shaped to flatter it.
Retrieval arm
Tokens, 10 tasks
Mean per task
Target files delivered
A. Chart alone
15,024
1,502
0 of 27
A+. Chart, then view what it named
51,287
5,129
19 of 27
B1. Oracle views
53,463
5,346
27 of 27
B2. Grep and view
430,682
43,068
about 12 of 27
The reading
Against realistic discovery, the chart wins by roughly eight to one
on cost and delivers more. Arm B2 is what an agent without a map
actually does, and it spent 430,682 tokens to put about twelve
target files in front of the model. Arm A+ spent 51,287 to put
nineteen there. Per file delivered that is 2,699 against 35,890.
What it does not say
Nothing here claims the chart makes a model better. Proving that
takes live inference across a graded task set, several runs deep,
and that work has not been done. The corpus is also the repository
the packer was developed against, a real limitation, stated up
front rather than discovered later. The number to improve is 19 of
27, and it is mostly a ranking problem.
Roadmap
Every arc has to land before Shoal opens to users
An arc is one capability, and more are added as the work turns them up.
Each carries three states rather than one. Building it opens the other
two, so testing and the finish pass run alongside each other rather than
in sequence. Any row with a write-up opens on click.
24Arcs
20Built
3Designed
1Research
#
Arc
What it does
State
Build
Testing
Finish
01
The tree learns the vocabulary. Nothing survives under an old name.
Built
In testing
Polishing
02
The terminal surface. Live output, real scrollback, steer a turn while it runs.
Built
In testing
Polishing
03
Tool calls come out well formed by construction rather than repaired after the fact.
Built
In testing
Polishing
04
Launch is one binary, and the server outlives a single run.
Built
In testing
Polishing
05
A reviewer that cannot write, enforced by Shoal rather than asked of the model.
Built
In testing
Polishing
06
File size limits stop being advice and become machinery on the write path.
Built
In testing
Polishing
07
The codebase becomes something the agent asks about instead of searches.
Built
In testing
Polishing
Why it exists
In one recorded run, a model called view on the same file 65 times. Speculative exploration is the most expensive thing a small context window does, because the model has no cheap way to see the shape of a repository. It greps, guesses, reads a whole file to learn it was the wrong file, then carries that file in the window for every remaining turn of the session.
How it works
Shoal keeps a YAML adjacency graph at .shoal/chart.yaml, YAML because you are meant to read and edit it. Nodes are files, each carrying a line count, a language, its defined names, and one purpose line lifted verbatim from the file's own leading comment. No model writes that line, and it is re-derived rather than remembered, so deleting the comment removes it from the chart. Edges are Go imports resolved through go/parser, decomposition lineage verified by Bosun, and annotations you draw by hand. Discovery walks breadth-first from the nodes your task seeds and spends its budget nearest-first, so pressure costs detail before it costs coverage. The chart is served by a tool call and never injected. No standing copy lives in any prompt, results append at the tail of history, and a session that never asks a discovery question never pays for one.
The shape on disk. Hand-editable on purpose.
# .shoal/chart.yaml
version: 1
root: ~/shoal
nodes:
internal/chart/chart.go:
lines: 412
lang: go
purpose: The chart graph, its nodes and edges, and the YAML on disk.
purpose_from: comment# lifted verbatim, never generated
symbols: [Chart, Node, Edge, adjacency]
edges:
- from: internal/chart/chart.go
to: internal/chart/build.go
kind: import# import | lineage | manual
Still open
Import edges are Go only. Other languages get nodes, purposes and defined names but no derived edges, because the fallback deliberately does not guess. In a graph you hand-edit, a guessed edge reads as a fact. There is no ranking inside a distance ring beyond edge kind and the identifier prior, which is where the missing 30 percent of coverage lives. Bash operates outside the write hook, so a shell redirect can change a file the chart never hears about, and the pack step is what catches it. No live inference has measured the chart yet, so nothing here shows a model completing a task better because of it.
08
Context hygiene as a background pass that never disturbs work in flight.
Built
In testing
Polishing
09
Models become named connections, and something checks one fits before it loads.
Built
In testing
Polishing
10
One typed stream out, one channel in. Every surface becomes a thin client.
Built
In testing
Polishing
11
Outside tools come through one door. Credentials live where the model never sees.
Built
In testing
Polishing
12
Can what the context window discards be returned to, rather than only summarised?
Research
Waiting
Waiting
13
First run asks what you want to work on, on every surface, once.
Built
Waiting
Waiting
14
Delegated work keeps its record past the session that spawned it.
Built
Waiting
Waiting
15
History gets the graded treatment the codebase already has. Built, measured, and switched off by its own result.
Built
Waiting
Waiting
Why it exists
When a context window fills, Shoal compacts, dropping the oldest tool results whole to get back under the line. That is a cliff, and a cliff cannot tell a file your task depends on from a file you opened once and closed. The codebase already gets better treatment than that, since the chart hands a model a graded view of a file rather than all of it or none. The question this arc asked is whether history deserves the same gradient.
How it works
It does not, on the evidence, so it ships switched off. The gradient is built and tested. It is a fourth kind of janitorial edit that steps an old tool result down through outline, head, and finally a label that always names the tool and the file, so a condensed result stays identifiable and can be run again. It is off because a pre-registered measurement refused it, and refusing a feature you have already built is the only kind of refusal that costs anything. One number killed it. The gradient could not be driven to the compactor's token count on any of ten histories, because a result the live turn still refers to is exempt at every setting, so at the pressure where compaction actually happens the policy has no behaviour left. It keeps far more of what a task needs when cost is not matched, 0.83 against 0.34, and that is exactly the comparison that does not decide anything.
Still open
The arc's real finding was not the question it asked. Measured on this corpus, what the compactor keeps is anti-correlated with what the task needs, because its only ordering is recency. Ground truth gets read early, and the files that turned out not to matter get read late and survive on position alone. It has no relevance input at all, so read that as indifferent rather than working against you, and the sign is a property of this corpus rather than a law. Where to look next is a relevance signal that reads what the task is about instead of only what the last turn mentioned. The corpus is ten histories built over this repository, which is also the repository the code was written in.
16
Tighten the boundary the tools operate inside.
Built
Waiting
Waiting
Why it exists
A coding agent that can read and write any path on the machine is trusting the model to stay inside your project. That is an honor system, not a boundary. One read-only git tool already held that line, written so a reviewer could not reach out of the workspace, and the arc was to make the same line hold everywhere a tool takes a path.
How it works
One check decides whether a path is inside your workspace, and view, glob, grep, write, edit and patch all call it. The path is resolved first, symlinks and all, and then compared, because a link inside your project pointing outside it walks straight through a check that only reads the spelling. An absolute path or one that climbs out with .. is refused by name, telling the model what happened and what would work, and it is never turned into a question for you to approve. Write and patch check before they create a directory, so a refusal cannot arrive after the directories already exist. The boundary is threaded from your project onto the tool registry and stamped at the moment a call is dispatched, both kinds of delegated agent inherit it, and nothing inside a session can widen it.
Still open
Bash is not contained and Shoal does not claim it is. A shell cannot be bounded by inspecting its arguments, so that is a separate piece of work with a different shape. Reading a secret through bash is still open for the same reason, though the file-reading route to it is now closed whenever your workspace is not your home directory.
17
Undo that belongs to Shoal, not to your commit history.
Built
Waiting
Waiting
Why it exists
A wrong edit that costs nothing to revert is a retry. One that costs a manual cleanup is a reason to stop using Shoal. Local models carry higher per-turn variance than frontier ones, and in the measured corpus run three of four failures were the model working for twenty loops without converging. Undo built on your own commit history is the wrong shape. It can only reverse what Shoal itself committed, and it refuses when the last commit was somebody else's.
How it works
Shoal keeps a shadow worktree, a second git directory under the app data directory, seeded from your repository but never writing to it. A capture records a content-addressed tree and a restore puts named paths back, so your own history is never touched and your own commits are never made or unmade on your behalf. One constraint decides the shape. Bash operates deliberately outside the file writer, so a checkpoint built on write hooks would silently miss every file bash creates. The tree is captured at the turn boundary instead, not per write. Undo composes the two halves and says which ones it did. Rewinding the conversation alone tells you your files were not touched, and undoing to a point restores the files first and then cuts the history, so a restore that fails leaves nothing to explain away.
Still open
A restore is additive. It puts back what the capture held and never deletes, so a file you made after that point survives the undo and is named in the notice rather than removed behind you. That is the recoverable direction of a choice that has no safe answer, and it means undo is not a time machine. The cost of capturing a large tree has not been measured, and the capture runs at the start of every turn.
18
Long term priors, recalled on demand, earning their keep.
Designed
Waiting
Waiting
19
Named roles instead of one agent wearing every hat.
Built
Waiting
Waiting
Why it exists
Every piece of work Shoal handed to a sub-agent went to the same agent. A fresh context, every tool the main loop has, and the same instructions. Handing a review to something that can rewrite the file it is reviewing is a boundary you are asked to trust the model to respect, and asking for a quick look at how something works costs you an agent equipped to change it.
How it works
A delegation can name a specialist and get that specialist's toolset and its own instructions instead of the default. Two ship. The reviewer reads code and reports what is wrong with it, worst first. The scout finds something out and reports the facts, including what it did not find, so you can tell an exhaustive search from a lucky one. Both are read only, and read only here means the tools to change a file are not in the set the child was handed, so a call to one of them is refused by Shoal at the moment it is dispatched rather than by instructions asking the model to behave. A specialist names a toolset; it cannot list one, so there is no way for a named role to end up with more reach than the role was given. Naming none is unchanged, so a delegation gets the full toolset and the main loop's own instructions. A name that does not exist is refused with the names that do, because falling back to the ordinary agent would make a typo look like a working delegation. The roster reports which specialist each child ran as, in the terminal and in both windowed clients, and the record survives the session.
Still open
Both specialists that ship today are read only, so the enforcement that would matter to a specialist allowed to write is carried and has never fired. The roster is small on purpose and neither entry prefers a particular model yet, though a specialist can name one. And the list is compiled into the build rather than read from a file, so adding a role is a change to Shoal rather than something you write yourself.
20
Shoal with no human attached.
Built
Waiting
Waiting
Why it exists
Everything Shoal does assumes somebody is watching, ready to approve a write, read a diff, and steer a turn that wanders. Automation has none of that. A scheduled job, a check on every pull request, or a trigger from another system needs a Shoal that runs once, decides for itself, and exits with an answer a script can branch on.
How it works
shoal exec takes one prompt from an argument, a file, or standard input, runs it without asking anybody anything, and exits. Output is the final text, one result document carrying the stop reason and what it spent and which files it touched, or every engine event as JSON lines for something else to read as it happens. The exit code is distinct per outcome, so a scheduler can tell a completed run from a refused one without parsing anything. Approvals cannot ask a person, so they are answered by an autonomy ladder. One table maps what the engine already decided a call is onto the lowest rung that permits it. The floor is the default and it refuses anything that writes, and reports the refusal rather than dropping it quietly. Climbing takes a flag, and the top rung is not reachable by naming a level, only by asking for it outright.
Still open
The floor has one honest gap. What counts as read-only is decided before the ladder sees a call, so a shell command the permission engine already treats as read-only runs at the floor without the ladder weighing in. Tightening that is open work, and it is worth knowing before pointing an unattended run at a machine you care about. Session forking, isolated worktrees, queues and webhooks are deliberately not part of this.
21
Run the work on a machine that is not this one.
Designed
Waiting
Waiting
22
Getting the change onto disk right the first time, and counting every time it is not. The count is what exposed the failures arc 24 repairs.
Built
Waiting
Waiting
23
Know what is moored, and narrow what it is allowed to touch.
Designed
Waiting
Waiting
24
Any model, not one model. Know what a model can do before it loads, survive what it cannot, and measure the difference.
Built
Waiting
Waiting
Why it exists
A coding agent quietly takes the shape of whichever model its author had loaded. The tool-call format, the chat template, and the parser that reads a completion back all follow that one model's habits, and a different model then fails against them for reasons that look like the model's fault and are not. A twenty-task corpus run across four models on one machine found four distinct failures of exactly that kind, and every one of them was Shoal's to fix.
How it works
Shoal reads a model's tool-call support from its GGUF header before any weight loads, so shoal models and shoal doctor tell you when a model cannot call a tool and stop recommending one. When a dock refuses to parse a completion, Shoal retries once with the tools dropped instead of ending your turn, and counts the fallback in the tally as a degrade so the number is never hidden. A mooring can name the chat template llama.cpp renders with, which is the only way the two Mistral models are reachable at all. And a tool call a model writes into its message body is read back out of it, under a guard that is the point of the feature rather than a caveat on it. Every recognised call writes its own tally row, so a pass rate can always be re-derived with the recogniser subtracted. Measured live on three models, pinned and reproducible. gpt-oss-20b went 19/20 to 20/20 with zero recognised calls, which is the control saying the recogniser stays out of the way, and Qwen2.5-Coder 14B went 0/20 to 15/20 with all 138 of its tool calls read out of prose and none off the wire.
A mooring naming the template llama.cpp renders with. The flag forms are --chat-template and --chat-template-file, and llama-server only accepts an arbitrary template once --jinja is set.
Codestral and Devstral have both pieces they need, a named template and tools described in words, but neither has been loaded, so the claim that those two are reachable stays untested. Every result is one pass per model, so nothing here is a median. And the header did not predict what a model writes into its body. Qwen renders tool-call tags yet wrote fenced JSON 138 times out of 138, so the native count is zero across all three runs. That finding argues against the design that expected the header to steer the recogniser, and it is recorded rather than buried.
Sea trials // the release path
The arcs build Shoal. This path is what carries a finished build out
to people who are not the builder, and it starts once the arcs are
done.
AlphaInvite only. A named few, running it on their own work.
Closed betaA wider list, still by invitation, and large enough that the reports stop agreeing.
Open betaAnybody who wants it, with the known rough edges written down first.
Full releaseIt goes out under its own name, and there is no list to get on.
Get started
Build it and moor a model
You need Go and a llama.cpp release binary, or any OpenAI-compatible
endpoint you already run.
# Clone and build. The public repo is not open yet.
git clone https://github.com/{coming_soon}/shoal.git
cd shoal
go build -o shoal . # shoal.exe on Windows# Keep a server loaded between sessions
./shoal up --model ~/llama.cpp/models/gpt-oss-20b-MXFP4.gguf
./shoal --model ~/llama.cpp/models/gpt-oss-20b-MXFP4.gguf # adopts it
./shoal down# Or let each run spawn and clean up its own server
./shoal --model ~/llama.cpp/models/gpt-oss-20b-MXFP4.gguf --dock llamacpp
# Or attach to any OpenAI-compatible server you run yourself
./shoal --endpoint http://127.0.0.1:8820 --model <name>
Install it properly
shoal setup puts the binary on PATH and registers it with the OS. shoal setup --uninstall reverses it and keeps your data.
Check the hardware first
shoal doctor reads the platform. shoal models scans the models directory and recommends one that fits.
Read the docs
The docs cover install and first run on each surface, mooring a model, and a reference for every command, flag and configuration key.