A collaborative Linux desktop for people and AI agents, streamed to the browser over WebRTC
301
The agent runtime that drives a whole Linux desktop, as a plain MCP client over a unix socket. One static Go binary, two faces — a terminal session and the chat panel in the browser — over one loop, one policy and one
agent.db.
| Module | github.com/sentineldesk/agent |
| Language | Go 1.26, CGO_ENABLED=0 |
| Binary | sentineldesk-agent (linux/darwin × amd64/arm64) |
| Size | ~50k lines of Go across 125 files, 46 of them tests |
| Licence | Apache 2.0 — the name and logo are trademarks and are not covered |
| Deps | bubbletea (TUI), gorilla/websocket, creack/pty, modernc.org/sqlite (pure Go) |
Most agent runtimes are coding agents: they act on a repository, the unit of
work is a file, and the answer to "that was wrong" is git. This one acts on a
desktop — a screen, windows, a browser, a terminal somebody may be watching,
and a mouse there is only one of. Four consequences shape the whole codebase:
~/.sentineldesk/agent.db and into the desktop's own action log. The
invariant: the agent is either supervised by a person or leaving a trace,
with no third state.--role witnessed
substitutes visible tools for invisible ones (run_command → terminal_run)
in the runtime, not in the prompt — evidence that depends on a model
remembering to be observable is not evidence.Design rationale lives in stage-2-agent-engine.md;
models in providers.md; skills in skills.md;
attribution in NOTICE.md.
One binary, one loop, one database. Which face you see changes who is typing, not what is running.
| terminal | browser | |
|---|---|---|
| start | sentineldesk-agent | sentineldesk-agent -serve |
| drawn by | the TUI in this binary | the desktop's own chat panel (the agent dials, the desktop listens) |
| tool approval | shift+tab toggles ask-before-mutate | never per-call — the gate is request_control on the socket |
| history | agent.db | the same agent.db |
serve is this binary with no terminal attached. There is no server process
between them, no port, and no second implementation of the loop to drift.
cmd/sentineldesk-agent/ entrypoint, CLI verbs, serve, chat wire, sockets (~10.2k)
internal/
loop/ the agent loop: turns, guards, subagents, compaction (~8.6k)
tui/ terminal UI: chat, palette, panel, modals, cell buf (~13.2k)
provider/ model backends + catalogue + key handling (~4.0k)
transcript/ history/export rendering in three formats (~4.3k)
store/ sqlite: sessions, turns, tool calls, tokens, pricing (~1.9k)
mcpclient/ MCP client over the unix socket; gateway, events (~1.7k)
skills/ skill discovery, built-ins, catalogue, dump (~1.6k)
settings/ ~/.sentineldesk/config.json + migration (~1.0k)
update/ release check — reports, never installs (789)
delegate/ hand the loop to the installed `claude` binary (706)
toolsearch/ goal-ranked tool selection (shared with the server) (518)
memory/ facts the agent is told to keep (309)
frontdesk/ WebSocket auth handshake for remote front desks (213)
pkg/
frontmatter/ YAML frontmatter parser for SKILL.md
version/ build version
lang/ en / es / pt catalogues, compiled in
prompts/ system prompt + role and perception overlays
skills/ 21 authored skills, embedded in the binary
.sentineldesk/ this repo's own agents, commands and skills
loop — more than a while-loop around an API call: a refusal carries its
kind (room = ask a person and retry, policy = never), an interruption is
said out loud to the next turn (a cancelled command may have installed half a
package), and auto-compaction folds a long run before it dies on a context
limit.mcpclient — the security argument for a separate binary. Handles the four
things most MCP hosts ignore: progress, cancellation, denial kinds and events.store — tokens are recorded exactly as the provider reported them; money
is derived on the way out from a correctable rate. A model with no rate prints
?, never 0.toolsearch — one implementation, two callers on opposite sides of the
socket (the server's tool_search, and the runtime's local ranking).skills — the published SKILL.md format, reading the same directories
Claude Code and opencode do, so skills cross over unchanged in both directions.delegate — for long CLI-provider runs, hands the loop to the installed
claude binary rather than fighting its native tool-calling every turn.chatwire.go — a deliberate byte-for-byte copy of the desktop repo's
protocol file. A shared Go package would couple the two repositories at compile
time, which is exactly what the split exists to prevent.make build # this machine
make all # linux + darwin, amd64 + arm64
./bin/sentineldesk-agent # finds the socket, opens a session
./bin/sentineldesk-agent -doctor # 15 checks against a real desktop, no model
No API key is needed to reach a session — /connect inside it takes one, checks
it with the provider, and writes it to ~/.sentineldesk/config.json (0600,
refused rather than warned about if others can read it).
The socket is discovered by opening candidates in order ($SENTINELDESK_SOCK,
$MCP_SOCK, /run/sentineldesk/mcp.sock, $XDG_RUNTIME_DIR/…, the
sentineldesk-run docker volume) and every failure prints what went wrong at
each path. -container (docker exec) stays opt-in: a missing socket must never
quietly become something else.
doctor · run "goal" · serve · tools [query] · providers · costs ·
history [id] · export [id] · roles · memory · skills ·
skills install <repo> · skills-dump
costs, history, export, providers, roles, memory and skills need
no desktop.
--provider anthropic | ollama | ollama-cloud | openai | openrouter
--model any string — a model released this morning needs no new build
--role efficient (default) | witnessed
--tools goal-matched tools on top of the core set (default 12; 0 = all 120)
--max-turns stop a run after this many (default 25)
--sock name the socket instead of having it found
--container reach the desktop through docker exec
Cheap on purpose. A measured 21× reduction per run, from caching the prompt prefix and offering a goal-ranked subset of the catalogue instead of all 120 tools. Both compose: caching stops re-sending the catalogue, selection makes it smaller.
| tools offered | est. USD | |
|---|---|---|
| as it first worked | 120 | 0.0776 |
| prefix cached | 120 | 0.0098 |
| selection, cold cache | 17 | 0.0117 |
| both, warm cache | 17 | 0.0037 |
Any model, any hardware. Anthropic, OpenAI, OpenRouter, Ollama local,
Ollama Cloud, or the claude CLI's own login. With Ollama there is no key, no
bill and nothing leaves the machine.
Three languages compiled in — English, Spanish, Portuguese. It changes the
interface and the replies and never the tool arguments: a shell does not speak
Spanish. lang's own test fails the build on a missing key or a dropped %s.
No invented reasoning. No separate thinking channel is requested, so the model's prose is the whole of the recorded reasoning — and the export says so rather than manufacturing a reasoning section out of the tool log.
Fails loudly. A refusal carries its kind, so "wait your turn" and "never allowed" are never collapsed into "it did not work".
make test # go test ./... — offline, deterministic, costs nothing
make vet fmt tidy
make image # container image
make release checksums
The loop's tests run against a scripted provider and a fake server. A scripted provider is not a mock: it implements the same interface, so the loop under test is the loop that ships. It exists because the behaviours that matter — a refusal it must retry, one it must not, an interruption landing between two calls of one batch — are things a real model produces only by luck, and because a good model hides loop defects by reaching the right answer despite them.
Apache 2.0; every source file carries SPDX-License-Identifier: Apache-2.0.
SentinelDesk and its logo are trademarks of Federico Pereira and are not
covered by the licence. You may say your work runs on or is built with
SentinelDesk; you may not use the name or logo as the name of your own product,
or on a modified build in a way that leaves somebody thinking they had the
original. Rename the fork and the question disappears — the code is yours under
Apache 2.0 either way.
Content type
Image
Digest
sha256:70e87629f…
Size
71.6 MB
Last updated
21 days ago
docker pull cnsoluciones/sentineldesk-agent