typeclaw.json
Top-level config schema and reload behavior
JSON Schema–validated. typeclaw init scaffolds a $schema pointer at ./node_modules/typeclaw/typeclaw.schema.json so editors get autocomplete.
Top-level fields
| Field | Type | Reload | Concept |
|---|---|---|---|
models | Record<string, ref | ref[]> | live | model profile → ref or fallback chain |
port | number | restart-required | preferred host port; CLI falls back to ephemeral on conflict |
mounts | Mount[] | restart-required | host directories exposed inside the container |
plugins | string[] | restart-required | plugin module specifiers |
alias | string[] | live | additional names the agent answers to in channels |
channels | Record<adapter, AdapterConfig> | live | per-adapter config |
portForward | { allow, deny? } | restart-required | auto port-forward allow/deny lists; default { allow: '*' } |
network | NetworkPolicy | restart-required | egress filtering |
sandbox | SandboxPolicy | restart-required | per-tool bwrap sandbox options |
tunnels | Tunnel[] | restart-required | public URLs (see /concepts/architecture) |
roles | Record<name, Role> | match live, permissions restart-required | permission roles (see /concepts/permissions-model) |
docker.file | DockerFileOptions | rebuild via start | Dockerfile feature toggles + append lines |
docker.runArgs.append | string[] | rebuild via start | extra docker run flags |
git.ignore.append | string[] | rebuild via start | extra .gitignore lines |
Plugin-owned config blocks live at the top level under their plugin name and are validated by the plugin's own configSchema.
models
{
"models": {
"default": "openai/gpt-5.4-nano",
"fast": "openai/gpt-5.4-mini",
"deep": ["anthropic/claude-opus-4-8", "fireworks/accounts/fireworks/routers/kimi-k2p6-turbo"],
"vision": "openai/gpt-5.4"
}
}default is required. Other profile names are conventional but not enforced. Each value is either a single <provider>/<model> ref or a non-empty array forming a fallback chain: on failure, the runtime disposes the failed session and replays the prompt against the next ref.
Provider-family defaults override the SDK's reasoning level at session creation: OpenAI-family refs (openai/*, openai-codex/*) pin thinkingLevel: 'low' because GPT-5.x at the SDK default of medium pads reasoning tokens on routine tool-driven turns with no observable quality delta. Anthropic, GLM, and Kimi refs keep the SDK default.
network
{
"network": {
"blockInternal": true,
"autoAllowResolvers": true,
"allow": []
}
}| Field | Default | Effect |
|---|---|---|
blockInternal | true | DROP traffic to RFC1918, link-local (incl. cloud IMDS at 169.254.169.254), CGNAT, multicast/reserved, and IPv6 ULA/link-local/multicast. Loopback always allowed |
autoAllowResolvers | true | Narrowly carve out port 53 to nameservers in /etc/resolv.conf so VPC-internal DNS keeps working |
allow | [] | Explicit IPv4 CIDRs that punch through wholesale |
sandbox
{
"sandbox": {
"realProc": false,
"writablePaths": [".metabase-cli", "workspace/cache"],
"symlinks": [{ "from": "~/.metabase-cli", "to": "workspace/.metabase-cli" }]
}
}| Field | Default | Effect |
|---|---|---|
realProc | false | Opt into the stricter real-proc /proc strategy: mount a fresh, PID-namespace-scoped /proc in the per-tool sandbox for full PID isolation. Needs CAP_SYS_ADMIN, so setting true grants the container that cap. Not required for external package CLIs (bunx, bun add <pkg>, bun run <pkg-bin>) — the default proc-bind strategy already gives them a working /proc with no cap by --ro-binding the container's real procfs under a child user namespace that blocks /proc/<agent>/environ leaks. On runtimes where the real-proc mount is a no-op (OrbStack, rootless Docker, gVisor, Docker Desktop ECI, AppArmor hosts) the sandbox auto-falls-back to proc-bind regardless. See /internals/sandbox |
writablePaths | [] | Extra agent-root-relative directories made writable inside the per-tool bwrap sandbox, on top of the built-in zones (workspace/, public/, mounts/, .git). Use it when a low-trust role runs a CLI that insists on writing a fixed config dir under the agent folder. Entries must be relative (absolute paths rejected), contain no .. or null bytes, exist as a real directory (not a file or symlink), resolve inside the agent folder, and not land on a security-sensitive root (.git, .env, secrets.json, sessions, memory, .typeclaw, node_modules). Invalid or non-existent entries are silently skipped, never fatal. Only affects sandboxed (low-trust) roles; trusted/owner already run unsandboxed |
symlinks | [] | Array of { from, to } that creates a symlink and makes its target writable in one entry. from is the symlink location — an absolute container path (e.g. /root/.foo) or a ~/-prefixed path — and to is an agent-root-relative directory automatically added to writablePaths. The symlink is created at the real container $HOME for unsandboxed (trusted/owner) bash via the entrypoint, and inside the jail at the sandbox $HOME (/tmp) for low-trust bash. The entrypoint refuses to overwrite an existing non-symlink. from may not be /, point under /agent, or target the kernel/virtual roots /proc, /sys, /dev, /run. See /internals/sandbox |
mounts
{
"mounts": [
{
"name": "downloads",
"path": "~/Downloads",
"readOnly": true,
"description": "files to inspect"
}
]
}Each mount maps a host directory to /agent/mounts/<name> inside the container. path accepts absolute paths, relative paths resolved from the agent folder, and ~/~/.... readOnly defaults to false; use true for reference data the agent should inspect but not edit. Mount changes are restart-required because Docker bind mounts are fixed when the container starts.
The CLI can manage this block:
typeclaw mount add downloads ~/Downloads --read-only
typeclaw mount list
typeclaw mount remove downloadsdocker.file
| Toggle | Default | Effect |
|---|---|---|
gh | true | install GitHub CLI |
python | true | install Python 3 + uv |
tmux | true | install tmux |
ffmpeg | false | install ffmpeg |
cjkFonts | 'auto' | install fonts-noto-cjk so Chromium renders CJK glyphs; 'auto' installs only when the host locale is CJK (ja/ko/zh) |
cloudflared | false | install cloudflared for cloudflare-quick tunnels (tunnel add / channel add github flip this on automatically) |
xvfb | true | install Xvfb for headed Chrome |
claudeCode | false | install Anthropic's Claude Code CLI |
codexCli | false | install OpenAI's Codex CLI (@openai/codex via npm) |
append | [] | extra Dockerfile lines appended verbatim |
Boolean toggles can also take a version string ("gh": "2.40.0") to pin via apt-get install pkg=<version>. claudeCode and codexCli are boolean-only. cjkFonts accepts true/false/'auto': 'auto' (the default) resolves at typeclaw start from the host locale, while an explicit boolean forces the decision.
Hot reload
typeclaw reloadRe-reads typeclaw.json and applies hot-reloadable fields. Boot-only fields (port, mounts, plugins, portForward, network, tunnels) are captured once at container start; reload reports which changes require a restart.
Auto-migration
One migration step still runs on load: if typeclaw.json contains a channels.github.eventAllowlist that was seeded by typeclaw init, it is dropped and the file is rewritten with a descriptive commit subject. All other legacy shapes (dockerfile, gitignore, model, channels.<adapter>.allow[]) are no longer migrated — use the current field names directly.