Sign inSign up

cnsoluciones/sentineldesk-agent

By cnsoluciones

Updated 21 days ago

A collaborative Linux desktop for people and AI agents, streamed to the browser over WebRTC

Image
Machine learning & AI
Developer tools
Operating systems
0

301

cnsoluciones/sentineldesk-agent repository overview

Sentineldesk-agent — Repository Overview

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.

Modulegithub.com/sentineldesk/agent
LanguageGo 1.26, CGO_ENABLED=0
Binarysentineldesk-agent (linux/darwin × amd64/arm64)
Size~50k lines of Go across 125 files, 46 of them tests
LicenceApache 2.0 — the name and logo are trademarks and are not covered
Depsbubbletea (TUI), gorilla/websocket, creack/pty, modernc.org/sqlite (pure Go)

What this repository is

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:

  1. There is no undo, so there is a trail. Every run records the goal, every turn, every tool call with arguments and result, and what each cost — into ~/.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.
  2. Being observable is a setting, not a request. --role witnessed substitutes visible tools for invisible ones (run_commandterminal_run) in the runtime, not in the prompt — evidence that depends on a model remembering to be observable is not evidence.
  3. The desktop is shared, so concurrency is mechanical. The runtime reads the server's per-tool annotation: work that touches nobody's screen runs in parallel, anything reaching the display takes its turn.
  4. One security boundary. The agent is an ordinary MCP client and holds no privilege the socket does not grant. It never imports the server's dispatch; a policy overlay may only narrow what the socket allows. No ports, no network between agent and desktop.

Design rationale lives in stage-2-agent-engine.md; models in providers.md; skills in skills.md; attribution in NOTICE.md.


The two faces

One binary, one loop, one database. Which face you see changes who is typing, not what is running.

terminalbrowser
startsentineldesk-agentsentineldesk-agent -serve
drawn bythe TUI in this binarythe desktop's own chat panel (the agent dials, the desktop listens)
tool approvalshift+tab toggles ask-before-mutatenever per-call — the gate is request_control on the socket
historyagent.dbthe 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.


Repository layout

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
Package notes worth knowing
  • 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.

Getting started

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.

Commands

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.

Flags
--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

What it does that is unusual

  • 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 offeredest. USD
    as it first worked1200.0776
    prefix cached1200.0098
    selection, cold cache170.0117
    both, warm cache170.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".


Development

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.


Licence and trademark

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.

Tag summary

Content type

Image

Digest

sha256:70e87629f

Size

71.6 MB

Last updated

21 days ago

docker pull cnsoluciones/sentineldesk-agent