Signalbox is a personal, self-hosted platform for durable LLM-assisted work — your own always-on agent and chat hub rather than an account on someone else's product. One central daemon owns your sessions and keeps them alive across restarts, disconnects, and device switches; terminal, web, macOS, and iOS clients connect to it from anywhere, and runners you operate execute tools on your own machines.
The vision and target model describe the purpose, deployment shape, and destination in full; the target model details these capabilities directionally — accepted records decide them — and several (fork selection, delegation, steering consumption) remain open decisions.
Status: early implementation phase; APIs, protocols, and storage details are not yet stable.
Terminal Web macOS / iOS
\ | /
+-----------+-----------+
|
[ Central daemon ] ---- [ Postgres ]
| |
provider adapters | scheduler / tool policy
|
outbound connections
/ \
[ambient runner] [restricted runner]
The daemon is the source of truth; a client device and an execution machine need not be the same machine. See Architecture for the boundaries and important qualifications behind this sketch.
- Vision
- Target model
- Architecture
- Glossary
- Scenarios
- Invariant test index
- Domain spine
- Testing style
- Open questions
- Living specification
Project participation is described in CONTRIBUTING.md, security reporting in SECURITY.md, and repository guidance for coding agents in AGENTS.md.
Install rustup. The repository's rust-toolchain.toml
makes rustup select the pinned minimal stable toolchain with rustfmt and Clippy.
Non-cargo tooling comes from the devenv environment. With
Nix and the devenv CLI installed, use devenv shell to enter it; direnv users
can instead allow the committed .envrc. The Postgres integration suite still
needs a running Docker daemon. See AGENTS.md for the authoritative
tooling, formatting, and validation workflow.
The workspace contains the dependency chain apps/signalboxd →
crates/application → crates/domain, with crates/persistence depending on
both crates/application and crates/domain, and the dev-only
crates/expect-table consumed by the domain crate's tests. Before finishing any
change, run the repository-wide validation sequence in AGENTS.md —
the canonical list of required commands and their setup notes — from the
repository root.
devenv up starts a dev instance: a PostgreSQL cluster on loopback and one
signalboxd built from the working tree. The cluster asks for port 54341 and
devenv allocates upward from there if it is taken, so the port is resolved
rather than fixed — echo $PGPORT inside devenv shell names the one in use,
and the daemon is given the same resolved value. The devenv configuration owns
what devenv up launches, in what order, and why test databases stay
deliberately outside its scope; everything below is operational usage.
State lives under the gitignored .devenv/state/: the cluster in postgres/,
and everything the daemon needs in dev-instance/ — a locally generated
certificate authority and server certificate under tls/, a process-scoped home
under home/, signalboxd.toml and session-templates.toml, seeded on first
run from config/signalboxd.example.toml and
config/session-templates.example.toml.
Both are left alone afterwards so local edits survive. Wipe the whole instance
with rm -rf .devenv/state, or reseed one catalog by deleting its file under
.devenv/state/dev-instance/.
Two things are worth knowing before editing the seeded model catalog or reaching
for the socket. Its seed is a copy of the checked-in example, so it carries that
file's undated family names such as claude-haiku-4-5; the spelling a
provider_model must take is stated in
configuration and credentials,
and how a reported identity is related back to it — including the dated snapshot
a family name resolves to — in
provider-target identity.
And the process socket lives at $DEVENV_RUNTIME/signalbox/signalboxd.sock
rather than directly in the runtime directory, because the daemon accepts only a
socket parent meeting the ownership and permission rules the
process protocol
states. The devenv dev-instance launcher creates that directory and sets mode
0700 before executing the daemon; the daemon then binds the socket there. The
devenv shell exports that path as SIGNALBOX_SOCKET_PATH and provides a
signalbox <verb> convenience. That convenience execs Cargo's resolved binary
directly rather than through cargo run, so a shell carrying an ambient
-C prefer-dynamic (in RUSTFLAGS or inherited Cargo configuration) produces
an executable that needs Cargo's runtime library search path, which the direct
exec does not set; the command then exits 127 naming the missing shared
object. This is a recorded, loud failure under an unusual global setting, not a
silent one, and is left as a known limitation rather than reproducing
cargo run's environment here.
The daemon's default Anthropic key path is
$HOME/.config/signalbox/anthropic-api-key, written into the seeded model
catalog's Anthropic credential profile rather than passed in the environment,
and overridable with SIGNALBOX_DEV_ANTHROPIC_API_KEY_FILE at the moment that
copy is seeded; edit the seeded catalog to change it afterwards. The default
code-host token path is $HOME/.config/signalbox/github-token, overridable with
SIGNALBOX_DEV_GITHUB_TOKEN_FILE. The devenv Brave key path defaults to
$DEVENV_STATE/dev-instance/brave-api-key and is overridable with
SIGNALBOX_DEV_BRAVE_API_KEY_FILE. No credential material is committed or
generated. The
credential lifecycle
owns when those files are read, what their bytes mean, and how absence is
handled. Provision the default code-host path from the GitHub CLI, and create
the Anthropic and Brave paths for editing, with these one-line commands:
install -d -m 700 "$HOME/.config/signalbox" && (umask 077; destination="$HOME/.config/signalbox/github-token"; temporary="$(mktemp "$destination.XXXXXX")" || exit; trap 'rm -f "$temporary"' EXIT; gh auth token >"$temporary" && mv "$temporary" "$destination" && trap - EXIT)
install -d -m 700 "$HOME/.config/signalbox" && (umask 077; destination="$HOME/.config/signalbox/anthropic-api-key"; temporary="$(mktemp "$destination.XXXXXX")" || exit; trap 'rm -f "$temporary"' EXIT; if [ -e "$destination" ]; then cp "$destination" "$temporary" || exit; fi; editor="${EDITOR:-vi}"; EDITOR="$editor" sh -c 'set -f; $EDITOR "$1"' sh "$temporary" && mv "$temporary" "$destination" && trap - EXIT)
install -d -m 700 "$DEVENV_STATE/dev-instance" && (umask 077; destination="$DEVENV_STATE/dev-instance/brave-api-key"; temporary="$(mktemp "$destination.XXXXXX")" || exit; trap 'rm -f "$temporary"' EXIT; if [ -e "$destination" ]; then cp "$destination" "$temporary" || exit; fi; editor="${EDITOR:-vi}"; EDITOR="$editor" sh -c 'set -f; $EDITOR "$1"' sh "$temporary" && mv "$temporary" "$destination" && trap - EXIT)Most of devenv.nix exists to satisfy the ambient-configuration refusals that
configuration and credentials
specifies — the PG* and SSL_CERT_* scrub, the process-scoped home the
passfile check reads, the generated authority that lets a loopback cluster pass
full verification, and a fully stated DATABASE_URL exported to the daemon
process alone — so that experiments stop re-deriving them. Each is commented in
devenv.nix at the point it is handled.
The signalbox binary is the supported local terminal surface for the
process protocol. Point it at the daemon socket
with --socket or SIGNALBOX_SOCKET_PATH; signalbox --help lists the closed
command surface. For example:
cargo run -p signalbox-client -- --socket /path/to/signalbox.sock list
printf '%s' 'hello' |
cargo run -p signalbox-client -- --socket /path/to/signalbox.sock \
send 00000000-0000-4000-8000-000000000001The Docker-backed offline terminal-to-model smoke test is explicitly ignored:
cargo test -p signalbox-client --test end_to_end \
terminal_client_completes_an_offline_scripted_conversation \
-- --ignored --nocaptureThe companion ignored real-Anthropic path makes a live provider request and may
incur cost. The checked-in catalog names the production installation placeholder
for the exec supervisor, so first materialize a runtime copy with the host
executable that Cargo actually builds (inside devenv shell):
supervisor="$(tooling/resolve-cargo-bin.sh "$PWD/Cargo.toml" "$PWD/target" \
signalbox-tools-exec signalbox-exec-supervisor)"
signalbox-materialize-config config/signalboxd.example.toml \
target/signalboxd.live.toml "$supervisor"Review that runtime copy and point its Anthropic credential profile's file at
a mode-0600 file containing only the API-key bytes. The live path then runs
only when both opt-in values are supplied:
SIGNALBOX_E2E_CONFIG_FILE=target/signalboxd.live.toml \
SIGNALBOX_E2E_SELECTION_ID=a5fec003-0edd-4118-96d1-18af31157bd3 \
cargo test -p signalbox-client --test end_to_end \
terminal_client_completes_the_real_anthropic_path \
-- --ignored --nocaptureThe signalbox-debug binary is a local development harness, not the supported
terminal client defined by the
process protocol. Against a disposable local
PostgreSQL database it runs migrations, creates one session, submits one input,
lets the real scheduler execute a deterministic reply, and prints the terminal
semantic transcript:
SIGNALBOX_DEBUG_DATABASE_URL=postgres://signalbox:signalbox@localhost/signalbox \
cargo run -p signalboxd --bin signalbox-debug -- \
"hello" "scripted assistant reply"The debug database connection explicitly disables TLS and must not be used as production connection configuration.
The same harness can run the production runtime bridge against Anthropic. Review
the checked-in example, materialize target/signalboxd.live.toml with the setup
command above, and point its Anthropic credential profile at the mode-0600 key
file before running:
SIGNALBOX_DEBUG_DATABASE_URL=postgres://signalbox:signalbox@localhost/signalbox \
SIGNALBOX_CONFIG_FILE=target/signalboxd.live.toml \
cargo run -p signalboxd --bin signalbox-debug -- \
--anthropic a5fec003-0edd-4118-96d1-18af31157bd3 \
"Reply with exactly: signalbox smoke ok"Production process configuration is specified in configuration and credentials. The process boundary is specified in the process protocol; model configuration and credential delivery are specified in configuration and credentials.
Signalbox is licensed under the MIT License.