Imported from zeroaltitude/theseus (
crates/theseusd/AGENTS.md). Install upstream withnpx skills add zeroaltitude/theseus --skill theseusd. Copyright stays with the author.
theseusd
The daemon binary: the kernel behind the protocol, on a Unix socket (the default) or --stdio. Also its
subcommands: job-wrapper, check, config, example-config, example-bindings, restore, and install.
example-config prints the template, byte for byte, and config --sparse prints the loaded config cut to what
differs from the defaults, which loads to the same config (theseus_core::config::sparse_note, theseus-vwar).
Key modules: main.rs, web.rs, install/. Read by: (a binary).
What's here
src/main.rs: the start, in the order serve-first requires: the token, the config, secrets resolving in the background, the store, the kernel's startup, then serving. The network, and every fsync but the kernel's one, go inafter_serving, as do the actors (the harness loop, the driver, telemetry, the web UI, Discord), which start as soon as the socket answers. Also the signal arms (SIGINT and SIGTERM are one clean stop) and the reaper.- The index tender (row 51):
after_servingstarts the core's supervisor (theseus_core::tender) as soon as the socket answers, the socket daemon only, and the supervisor starts a tender 2 s later (one an exec kept is taken over at once); the reaper hands it each tender's exit; a stop sends the tender SIGTERM and never waits. It runs thetheseus-indexbeside this binary, never one on PATH. - The L1 role (M4 17b):
job-sandbox, an L1 job's init, dispatched first inmain, before the umask, clap, and tracing. Nothing probes L1 at the start:checkruns its self-test on demand (/bin/truein L1 from the check's own process,Sandbox::self_test), and health reports the last real L1 launch (theseus-gyin). An L1 job has no cgroup.tests/sandbox.rsruns real L1 jobs, its state dir and socket inside the workspace so the view's hiding is what keeps them out. - The units ask for
Delegate=yes(theseus-a5nv): the daemon stays in its unit's cgroup, readied after serving (theseus_core::cgroup), and each L0 job's command is born in a threaded cgroup of its own below it. A restart while a job runs starts as any start does, so there is no stop hook.tests/cgroup.rsproves the stop, the cap, and the restart in delegated scopes and units, and skips where there is no user systemd. hand(step 40, theseus-mgw.6): a hand, the job wrapper inside AWS, run in the hand image (infra/aws/hand/) as Lambda's bootstrap or a Fargate task's command, never by hand. It reads no config, store, or secret: its spec (in the Lambda event, orTHESEUS_HAND) is its whole input. Its code istheseus_core::aws::hands::hand.- The MCP server's L1 role (M7 43a):
mcp-sandbox, dispatched second inmain, afterjob-sandboxand before anything else, so it spawns the server's init from its main thread (theseus_kernel::mcp_l1). The board spawns it as an ordinary stdio child.tests/mcp_l1.rsruns a real server in L1 (as root, only L1's refusal; run it as an ordinary user, uid 65534 undersetprivon a root-only machine).theseus-sim fake-mcpends when its stdin does, so a kill test needs--outlive-stdinto see the role's watch of the daemon (theseus-grxh). job-wrappercatches SIGTERM from its first moments (M4 18a): a cancel asks it alone, and it stops its job's whole tree (theseus_kernel::tree), an L1 job through its init, then answers in the spool.tests/job_wrapper.rsstops real trees, asetsidsleeper included;tests/sandbox.rsan L1 job's.--one-shot SECS(with--stdio, theseus-mqxk):theseus --spawn asksays the daemon serves one run, which it follows for at most SECS after its turn; set on the core before serving (one_shot.rs).tests/spawn_follow.rsruns the follow through the real CLI;tests/continuations.rsholds the socket daemon unchanged.- Connections (theseus-7vtp):
serve_socketraises the softRLIMIT_NOFILEtoward the hard one before it serves (theseus_core::conns, at mostSOFT_TARGET, 65,536, since jobs and terminals inherit it; both numbers logged at info), holds socket connections to[server] max_connections(0: the limit less 256, at most 8192) by one counter, and tells one past it a protocol error (-32007, "the daemon holds N connections, its ceiling; close one and retry") before it closes it. An accept error is logged (one line a second, with a count) and waited 50 ms, never returned: nothing in the loop ends the daemon but a stop. A debug build'sTHESEUS_TEST_ACCEPT_ERRORS=Nfails the first N accepts. The other accept loops: the web UI's is axum's (it logs and sleeps 1 s on an error), the harness's notify socket takes the same rule, and the egress proxy's waits out EMFILE and its kin. The units carryLimitNOFILE=65536andTasksMax=49152.tests/connections.rs;scripts/conn-watchers.pyis the live check (watchers past 1,100). src/web.rs: the web server for the cockpit, at/. It embedscockpit/dist(withallow_missing), redirects the cockpit's old/cockpit/…to the same route at/…, and refuses a wrongHostorOriginand any uid but the daemon's own.src/install/:theseusd install, the daemon as a systemd service (--user, or--separateas root). It prints a plan and changes nothing;--applyperforms it,--checkcompares, and--removeis the inverse. A--userunit's token file is checked bystatalone and never opened (token.rs): a regular file, the operator's, mode 0600 or stricter, not empty, else the plan refuses and says the fix, and so does--apply, before it writes anything.--op-token-fileis a global flag, so it works before the subcommand and after it; the plan's hint is the command as typed with it added.--applywith no token file named refuses unless--token-from-drop-insays a drop-in supplies it (a plan only notes it). Both units restart after 1 s and stop a crash loop at 10 starts in 300 s (StartLimit*).--user --unit NAMEwrites a second daemon's unit,NAME.service, whose description and drop-in hint name it; the default unit's text is as it always was, so an installed unit still matches its plan.scripts/user-service.shwraps all of this (docs/user-service.md), andscripts/setup.shwraps that.- The config's source (theseus-5aqz):
--config, elseTHESEUS_CONFIG, else~/.theseus/theseus.tomlif it exists, else/etc/theseus/theseus.toml(theseus_core::config::find_config), found once inmainbeforeinstall's dispatch, so a plan writes the file the lookup found into the unit.configandcheckname the source and how it was found; neither file isNO_CONFIG. A debug build'sTHESEUS_TEST_SYSTEM_CONFIGstands in for/etc/theseus/theseus.toml, so a test never reads the machine's: set it in any test that runstheseusdwithout--config(tests/default_config.rs). cockpit/dist/: the cockpit's build, ignored. In a debug build it is read as it is served, so the suite's tests of/see the build the gate made before them.
Invariants
- Serve first. Nothing on the start path waits for a secret, the network, or a model, and no new work joins it
without its bench row (
theseus-sim bench lifecycle). - A config in the vault starts from its last-known-good copy, and acts on it at once when its sha256 is the one
the daemon recorded in the store as it wrote it (theseus-zmgb); an edited copy makes the start exec itself to read
the vault first. After serving, the vault is read once, and a changed note restarts the daemon in place: an exec
of
/proc/self/exe, with the same pid. - Every clean stop is one path: the
shutdownmethod, SIGINT, SIGTERM, and a restart onto a changed note. Each writesserver.stoppingand checkpoints, so the next start replays nothing. The store's close takes the stop's last checkpoint again, so a record a task the stop never waited for writes after it is not replayed either: the driver'sdriver.startedrow did, when a stop came as the daemon began to serve (theseus-fts6). The stop's answer is written before the daemon stops. - A serving daemon is a child subreaper (
children::adopt): a job's orphans are its to adopt and reap.job::DAEMON_VALUE_FLAGSmust match clap's options; a test here holds them together. - One index tender per state dir, and none outlives its daemon: it holds
<state>/index/LOCK, exits when the daemon's pid does (--parent), and after a restart in place the new image takes it over (children::relearnknows it by its command line). A test config from the template turns[index]off (common::safe_note): a tender would read the operator's model files.tests/tender.rsturns it on, with the real binary. - Files are the operator's alone: umask 077 before anything is created, and the state dir, store, and spool 0700. A job's command gets the operator's own umask back.
- A panic writes a crash file, then aborts (
panic = "abort", Review 2's consideration 1).daemon()installs the hook once the state dir exists; it writescrash-<mode>.jsonbeside the store.after_servingtakes it intocrashes/, logs it, writesserver.crashed, and health reports the newest. A debug build panics there whenTHESEUS_TEST_PANIC=after_serving(crash::planted), fortests/crash.rs; a release build has no plant. - Code that runs as root (
install --separate): every deletion is one planned file, an empty directory, or a socket, never recursive; ownership changes uselchown; account tools run by absolute path;userdelnever takes the state dir.
Tests
tests/*.rsrun the real binary.tests/common/mod.rshasDaemon, which kills and reaps its process when dropped: use it for every spawned daemon (an explicit stop alone once leaked one for 45 minutes).tests/common/model.rsis a stand-in Messages API: tool calls per prompt, andFakeModel::requests()keeps every request, so a test reads which model each turn asked for.tests/postmortem_incident.rs(theseus-m2tiu) scripts an incident on a real daemon (a task with a 33 s command, a failing tool, a question answered and one left to expire, akill -9mid-turn and the restart, a wake that comes due while it is down), asks the realtheseus postmortemfor the report, and, withTHESEUS_RECORD_POSTMORTEM=<dir>set (it takes about 40 s, so it runs only then), writes the incident's ledger rows there as the fixturecrates/theseus/tests/fixtures/postmortem/incident.jsonl. A real incident:theseus postmortem --since 6h --rows-out incident.jsonl, then read the file for names and paths before it is committed.tests/user_service_script.rsrunsscripts/user-service.shand the realtheseusd install --useragainst stand-insystemctl,loginctl,journalctl,theseus, andopin a scratchHOME(the config tests also stand in fortheseusd's plan). It touches no real unit or daemon.- The rigs that need a job's real environment (
job_approval.rs,reaping.rs,broker.rs) use a fakeopand a file config, never real secrets.
Traps
-
A value the core keeps must not hold the core. Anything stored in
Core(a closure, a handle, a board's reader) that reaches anArc<Core>again is a cycle: the core and its store outlive the runtime, redb never closes, and the next start repairs the whole index (install #29: the MCP server's health closure held the server, which held the core; only theshutdownmethod's wake broke it, so a SIGTERM leaked 9 store handles and the serving start went from 1 s to 8.5 s). Hold the other end byWeak.tests/store_close.rsstops a daemon with every service on, byshutdownand by SIGTERM, and fails onoutlived the runtimein the log or a repair at the next start; the warning now namescore=(liveArc<Core>s) and the threads left. -
A test script's wait loop must end when its temp dir is gone, or a failing test leaves it looping on a deleted file.
-
A daemon test's live peer is traced through
/proc: run inside a job, an approval it sends is refused. Such tests skip that part inside a job, and say so on stderr. -
A
--stdiodaemon's pipes are copied by two plain threads to and from a socket pair (src/stdio.rs, theseus-xbtr), never read by tokio's stdin: that read sits on the runtime's blocking pool until the client closes its end, so the runtime could only be shut down with a bound, and a task still holding the core past it left the store open as the process ended (redb unclosed, the stop's last checkpoint lost, the whole run replayed). The runtime's drop now waits for its tasks in both modes, and before a restart in place's exec too, which then waits up to 500 ms for the stdout thread as a stop does, so no line is cut at the exec (theseus-jo7f). The stdio arm registers theshutdownmethod's wake before serving, asserve_socketdoes:notify_waiterswakes only the waiters registered when it is called (theseus-yg1y).THESEUS_TEST_HOLD_CORE_MS(a debug build's plant, in both modes) holds the core past a stop's start, fortests/versions.rsandtests/config_copy.rs, andTHESEUS_TEST_LATE_WRITE(another) writes one record after the stop's last checkpoint (theseus-fts6), andTHESEUS_TEST_HOLD_CORE_THREAD_MSholds the core on a thread of the daemon's own, which the runtime's drop does not wait for (stdio::planted_thread_hold): the stop waits for the store's close after the drop (leak::wait_closed, 2 s), so a thread mid-unit at exit leaves no handle behind (theseus-q0re4;stop_checkpoint.rs). The tests'--stdioclient istests/common/stdio.rs, each answer awaited with a bound. -
A stop or a restart that comes as the daemon begins to serve meets after_serving's startup work. A test that checks what the next start replays holds the stop until the daemon has settled:
config_copy.rsholds the fake vault's read (hold-read) untilsettled, and never past half ofop'sOP_TIMEOUT(10 s), after which the daemon gives up on the read and no restart comes (theseus-fts6).settledleaves out the history check, which waits out a busy machine. -
A wrapper's stop SIGTERMs every process it meets in its job's tree through the grace, one born after the first signal too, so a test job's SIGTERM trap must not fork for what it records:
stops.rs's took its time withdate, which the wrapper's next look SIGTERMed, and left the file empty (theseus-y0lm). Take it in the shell (bash's$EPOCHREALTIME), and ignore SIGTERM before anything the trap forks. -
A start right after a stop waits up to 3 s for the store's lock. A check that reads the store's files waits for the old process to exit first.
