Configuration
Every field the daemon reads, taken from src/config/schema.ts. A key that is not one of these is a refusal to start rather than a setting quietly ignored, so a misspelling says so.
The file is read from ~/.config/errand/config.json, then /etc/errand/config.json, then config.json in the working directory. ERRAND_CONFIG names one outright.
There is a file to copy in the repository, config.example.json, and a schema beside it. Naming the schema in your own file gives an editor completion and checking as you type:
{ "$schema": "https://raw.githubusercontent.com/QaidVoid/errand/main/config.schema.json" }top level
The fully resolved configuration the daemon runs on.
| field | type | default | what it does |
|---|---|---|---|
projectRoot | string | required | Absolute path under which every session's project directory lives. A session works in a subdirectory of this and nowhere else. |
stateDir | string | required | Where per-session state directories are created on the host. |
chat
Chat connection and who may drive the bot.
| field | type | default | what it does |
|---|---|---|---|
token | string | required | Bot token. Secret. Never enters a sandbox. |
channelId | string | required | The single channel the daemon serves. Everything else is ignored. |
allowedUserIds | string[] | required | Accounts permitted to drive sessions. Must not be empty. The single entry ALLOW_EVERY_USER opens it to everyone who can post in the served channel. That is a deliberate, visible choice: an empty list is still a refusal to start, so nobody arrives at open access by leaving a field blank. |
blockedUserIds | string[] | none | Accounts refused before anything else is considered. Ahead of the allowlist and of any session role, so excluding somebody is one decision rather than an audit of every list they appear on. |
operatorUserIds | string[] | none | Accounts that may control any session, not only their own. |
agent
Which model the agent talks to, and the credential it reaches it with.
| field | type | default | what it does |
|---|---|---|---|
provider | string | required | Provider id, such as anthropic or zai-coding-cn. |
model | string or undefined | none | Model pattern or id. Omit to use the provider's default. |
visionModel | string or undefined | none | Model an image is shown to when the working model cannot see one. Omit and the cheapest model of the same provider that accepts images is used. Naming one here is how that choice is made deliberately. |
credentialName | string | required | Environment variable the agent reads, such as ANTHROPIC_API_KEY. |
credential | string | required | The credential value. Secret. |
agent.delegate
A cheaper model the session's model may ask about one artefact. Absent means no delegation at all: the session's own model does everything, which is what it did before this existed.
| field | type | default | what it does |
|---|---|---|---|
model | string | required | The model asked. Must be one the provider serves under the same key. |
perTurn | number | 8 | How many delegations one turn may make before the rest stay at home. |
deadlineMs | number | 60000 | How long one delegation may take before it is abandoned. |
baseUrl | string or undefined | none | Where the provider is reached, when the model store does not say. Read from the agent's own model store by default, so the same endpoint that serves the session is the one asked. |
github
The GitHub identity a session works with, when one is configured. Optional: a session with no GitHub configuration still runs, and simply has no credential to reach a repository with.
| field | type | default | what it does |
|---|---|---|---|
token | string | required | Token the agent authenticates with. Secret, and reachable by the agent. |
userName | string | required | Name commits are authored with. Free text: where a fork lands is read back from the API, so this does not have to be the bot's login. |
userEmail | string | required | Email commits are authored with. |
sandbox
What a session may consume, and what the backend enforces.
| field | type | default | what it does |
|---|---|---|---|
backend | SandboxBackend | "bailey" | Which backend confines sessions. |
requireFullEnforcement | boolean | true | Refuse to start when the backend cannot enforce every configured guarantee on this host, rather than reporting the gap and continuing. |
network | NetworkMode | "restricted" | Network exposure granted to a session. |
image | string | "localhost/errand-agent:latest" | Container image the podman backend runs. Inert under bailey. |
memory | string | "4g" | Memory ceiling per session, in size syntax such as 4g. |
cpus | number | 2 | CPU ceiling per session, in cores. |
pids | number | 512 | Process count ceiling per session. |
fileMax | string | "1g" | Largest single file a session may write, in size syntax. |
disk | string | "5g" | How much a session may add to its project and state together. Measured rather than enforced, because no backend caps what a process tree writes in aggregate without root. Passing it ends the session. |
diskCheckMs | number | 30000 | How often a session's disk use is measured, in milliseconds. |
gracePeriodMs | number | 10000 | How long a sandbox may take to stop before it is killed. |
output
How much of the agent's activity reaches the thread.
| field | type | default | what it does |
|---|---|---|---|
forwardToolOutput | boolean | false | Whether tool output bodies are posted, not just that a tool ran. |
maxToolOutputChars | number | 1500 | Longest tool output posted before it is truncated and marked as such. |
maxAttachmentBytes | number | 5242880 | Largest attached file taken into a session, in bytes. Well under what the chat service itself allows, on purpose: the limit is what is sensible to hand an agent, not what can be uploaded. |
maxAttachmentsPerMessage | number | 4 | Most attached files taken from one message. The rest are refused. |
postDiffs | boolean | true | Post a diff after the agent changes a file. A diff shows intent rather than contents, which is both smaller and less likely to put something private in a channel than uploading whole files. |
web
The local web interface. Absent means no listener at all, which is how the daemon behaves without one. There is no login: the address it binds to is the access control, and that address is checked rather than trusted.
| field | type | default | what it does |
|---|---|---|---|
host | string | "127.0.0.1" | Address to bind to. Must be loopback, private, or a tailnet address. |
port | number | 8787 | |
observer | boolean | false | When true the interface may watch and read but not start, prompt, or control anything. |
publicUrl | string or undefined | none | Where the interface is reachable from outside, such as behind a tunnel. Used to link a session from somewhere that is not the chat service, so a pull request can name the conversation that asked for it. Absent when the interface is not published, in which case no such link is offered. |
shutdown
Who may power off the host from a chat message. Off unless the list has somebody in it. This is the one command that acts on the machine rather than on a session, so it is not covered by any session role: whoever starts a thread owns it, and owning a thread is no reason to be able to turn the computer off.
| field | type | default | what it does |
|---|---|---|---|
allowedUserIds | string[] | none | Account ids permitted to power off the host. |
limits
Bounds on how much work exists at once.
| field | type | default | what it does |
|---|---|---|---|
maxConcurrentTurns | number | 2 | Sessions that may have a model turn in flight simultaneously. |
maxLiveSessions | number | 4 | Sessions that may exist at all. |
maxQueueLength | number | 32 | Prompts that may wait for a turn slot. |
maxQueueWaitMs | number | 900000 | How long a queued prompt may wait before it expires unsent. |
timeouts
Deadlines that end or unblock a session.
| field | type | default | what it does |
|---|---|---|---|
idleMs | number | 1800000 | No message and no agent activity for this long ends the session. |
startupMs | number | 60000 | How long the agent has to become ready before it is abandoned. |
questionMs | number | 300000 | How long a question posted to a thread waits for an answer. |
abortMs | number | 15000 | How long an abort waits for the agent before it is forced. |