Sacred boundary for AI agents. Filesystem isolation via seatbelt (macOS) and bubblewrap (Linux). YAGNI containers.
Temenos is a daemon that sandboxes command execution for AI agents. It listens on a unix socket and exposes an HTTP API for running commands inside isolated environments with configurable filesystem allowlists.
Containers are heavy. AI agents don't need network namespaces, layered filesystems, or image registries. They need one thing: don't let the LLM rm -rf /. Temenos uses the kernel's own sandboxing — seatbelt on macOS, bubblewrap on Linux — to deny-default the filesystem and allowlist only what the agent needs.
brew install tta-lab/ttal/temenosgo install github.com/tta-lab/temenos/cmd/temenos@latestDownload the binary from GitHub Releases.
# Install and start the daemon as a launchd service (macOS)
temenos daemon install
# Check it's running
temenos daemon status
# Run a command in the sandbox (via the Go client or curl)
curl --unix-socket ~/.temenos/daemon.sock http://temenos/run \
-X POST -H "Content-Type: application/json" \
-d '{"command": "echo hello from the sandbox"}'The daemon listens on a unix socket at ~/.temenos/daemon.sock (override with TEMENOS_SOCKET_PATH).
temenos daemon install # install as launchd service + start
temenos daemon uninstall # remove launchd service
temenos daemon start # start via launchctl
temenos daemon stop # stop via launchctl
temenos daemon restart # restart via launchctl kickstart
temenos daemon status # check if runningOn macOS, daemon install writes a LaunchAgent plist to ~/Library/LaunchAgents/ with RunAtLoad and KeepAlive enabled. Logs go to ~/.temenos/temenos.{stdout,stderr}.log.
Temenos is configured via ~/.config/temenos/config.toml (override with TEMENOS_CONFIG_PATH). The config declares daemon settings and baseline filesystem and environment allow-lists for every sandboxed command.
# ~/.config/temenos/config.toml
# Filesystem paths the sandbox may READ (read-only bind mounts).
allow_read = [
"~/Code",
"~/.config",
]
# Filesystem paths the sandbox may WRITE.
allow_write = [
"~/.ttal",
]
# Environment variable names allowed into the sandbox.
# A built-in baseline (USER, LANG, LC_*, HOME, PWD, SHELL, TZ, NO_COLOR, ...)
# is ALWAYS applied — entries below extend it.
# Glob wildcards use filepath.Match (e.g. "TTAL_*" matches "TTAL_JOB_ID").
allow_env = [
"TTAL_JOB_ID",
"TTAL_AGENT_NAME",
]
# Optional defaults, shown here for reference. Uncomment to override.
# Admin socket path.
# socket_path = "~/.temenos/daemon.sock"
# Seconds to wait before long commands move to a background job.
# auto_background_after = 30Callers cannot extend allow_env or change auto_background_after per-request — both are intentionally operator-only. See docs/sandbox-security-model.md for the full security model.
Config keys:
| Key | Type | Default | Notes |
|---|---|---|---|
allow_read |
[]string |
[] |
Read-only paths mounted into every sandboxed command. ~ is expanded. |
allow_write |
[]string |
[] |
Read-write paths mounted into every sandboxed command. ~ is expanded. |
allow_env |
[]string |
[] |
Extends the built-in baseline env allow-list. Supports filepath.Match globs. |
socket_path |
string |
~/.temenos/daemon.sock |
Admin HTTP unix socket path. ~ is expanded. |
auto_background_after |
int |
30 |
Seconds before a still-running /run command becomes a background job. 0 means use the default. |
Temenos ships with a built-in baseline of universally-safe env keys
(identity, locale/time, standard paths, shell, terminal sizing, common
diagnostic flags, tmux session identity). Operator config in allow_env extends the baseline
— it does not replace it. The full baseline list and exclusion rationale
live in internal/config/baseline.go.
Keys excluded from baseline include PATH, TERM (injected by the
sandbox directly), SSH_AUTH_SOCK, *_TOKEN/*_SECRET/*_PASSWORD,
proxy vars, and XDG_*. Operators may still allow these explicitly if
they understand the trade-offs.
Execute a command in the sandbox.
{
"command": "ls -la /project",
"allowed_paths": [
{"path": "/project", "read_only": true},
{"path": "/tmp/workdir", "read_only": false}
],
"env": {"FOO": "bar"},
"network": true,
"timeout": 30
}Response:
{
"stdout": "...",
"stderr": "...",
"exit_code": 0
}Keys in env not matching the effective allow_env (baseline + operator config) are silently stripped before execution. Stripped keys are logged at debug level and surfaced in stripped_env_keys in the response.
Returns platform and version info.
Uses /usr/bin/sandbox-exec with an embedded .sbpl deny-default policy. Each execution gets a fresh temp HOME directory that's cleaned up after. Allowed paths are injected as parameterized rules in the policy.
Uses bwrap with namespace isolation (--unshare-all). Read-only binds for /usr, /bin, /lib, DNS, and TLS certs. Allowed paths are added as explicit bind mounts.
On NixOS systems, Temenos also adds /run/current-system/sw/bin to PATH and read-only binds /run/current-system/sw plus /nix/store, so system profile tools are available inside the sandbox.
- NoopSandbox — passthrough when
AllowUnsandboxed: true(for development) - UnavailableSandbox — always errors when no sandbox runtime is found
import "github.com/tta-lab/temenos/client"
c, err := client.New("") // uses default socket path
resp, err := c.Run(ctx, client.RunRequest{
Command: "echo hello",
AllowedPaths: []client.AllowedPath{
{Path: "/my/project", ReadOnly: true},
},
})
fmt.Println(resp.Stdout)The Docker image includes Organon binaries on PATH:
src— tree-sitter symbol-aware source reading/editingurl— web page fetching as markdownweb— web search (Brave API / DuckDuckGo)
- Output truncated at 64KB per stream (stdout/stderr)
- Request body capped at 1 MiB
- Default
/runexecution timeout: 20 minutes
When --cgroupv2-memory-limit is set, temenos enforces per-execution memory limits via cgroup v2. This requires running inside a Kubernetes pod with cgroup v2 delegation (memory + pids controllers delegated to the container).
# Set 128 MB memory limit per sandboxed exec
temenos daemon --cgroupv2-memory-limit 128 startRequirements:
- Kubernetes pod with cgroup v2 mounted
- Memory controller delegated to the container (default on most distros)
- No
SYS_ADMINcapability required — delegation provides the necessary permissions - The daemon fails fast at startup if the environment doesn't support it
Diagnostics: temenos doctor reports per-check cgroup v2 / k8s / memory-delegation status with remediation hints for any failed probe.
make build # build binary → ./temenos
make test # go test -v ./...
make lint # golangci-lint (v2)
make ci # fmt + vet + lint + test + buildMIT