errandRun a coding agent from a chat channel, in a sandbox it cannot escape.
source

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:

json
{ "$schema": "https://raw.githubusercontent.com/QaidVoid/errand/main/config.schema.json" }

top level

The fully resolved configuration the daemon runs on.

fieldtypedefaultwhat it does
projectRootstringrequiredAbsolute path under which every session's project directory lives. A session works in a subdirectory of this and nowhere else.
stateDirstringrequiredWhere per-session state directories are created on the host.

chat

Chat connection and who may drive the bot.

fieldtypedefaultwhat it does
tokenstringrequiredBot token. Secret. Never enters a sandbox.
channelIdstringrequiredThe single channel the daemon serves. Everything else is ignored.
allowedUserIdsstring[]requiredAccounts 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.
blockedUserIdsstring[]noneAccounts 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.
operatorUserIdsstring[]noneAccounts that may control any session, not only their own.

agent

Which model the agent talks to, and the credential it reaches it with.

fieldtypedefaultwhat it does
providerstringrequiredProvider id, such as anthropic or zai-coding-cn.
modelstring or undefinednoneModel pattern or id. Omit to use the provider's default.
visionModelstring or undefinednoneModel 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.
credentialNamestringrequiredEnvironment variable the agent reads, such as ANTHROPIC_API_KEY.
credentialstringrequiredThe 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.

fieldtypedefaultwhat it does
modelstringrequiredThe model asked. Must be one the provider serves under the same key.
perTurnnumber8How many delegations one turn may make before the rest stay at home.
deadlineMsnumber60000How long one delegation may take before it is abandoned.
baseUrlstring or undefinednoneWhere 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.

fieldtypedefaultwhat it does
tokenstringrequiredToken the agent authenticates with. Secret, and reachable by the agent.
userNamestringrequiredName 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.
userEmailstringrequiredEmail commits are authored with.

sandbox

What a session may consume, and what the backend enforces.

fieldtypedefaultwhat it does
backendSandboxBackend"bailey"Which backend confines sessions.
requireFullEnforcementbooleantrueRefuse to start when the backend cannot enforce every configured guarantee on this host, rather than reporting the gap and continuing.
networkNetworkMode"restricted"Network exposure granted to a session.
imagestring"localhost/errand-agent:latest"Container image the podman backend runs. Inert under bailey.
memorystring"4g"Memory ceiling per session, in size syntax such as 4g.
cpusnumber2CPU ceiling per session, in cores.
pidsnumber512Process count ceiling per session.
fileMaxstring"1g"Largest single file a session may write, in size syntax.
diskstring"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.
diskCheckMsnumber30000How often a session's disk use is measured, in milliseconds.
gracePeriodMsnumber10000How long a sandbox may take to stop before it is killed.

output

How much of the agent's activity reaches the thread.

fieldtypedefaultwhat it does
forwardToolOutputbooleanfalseWhether tool output bodies are posted, not just that a tool ran.
maxToolOutputCharsnumber1500Longest tool output posted before it is truncated and marked as such.
maxAttachmentBytesnumber5242880Largest 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.
maxAttachmentsPerMessagenumber4Most attached files taken from one message. The rest are refused.
postDiffsbooleantruePost 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.

fieldtypedefaultwhat it does
hoststring"127.0.0.1"Address to bind to. Must be loopback, private, or a tailnet address.
portnumber8787
observerbooleanfalseWhen true the interface may watch and read but not start, prompt, or control anything.
publicUrlstring or undefinednoneWhere 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.

fieldtypedefaultwhat it does
allowedUserIdsstring[]noneAccount ids permitted to power off the host.

limits

Bounds on how much work exists at once.

fieldtypedefaultwhat it does
maxConcurrentTurnsnumber2Sessions that may have a model turn in flight simultaneously.
maxLiveSessionsnumber4Sessions that may exist at all.
maxQueueLengthnumber32Prompts that may wait for a turn slot.
maxQueueWaitMsnumber900000How long a queued prompt may wait before it expires unsent.

timeouts

Deadlines that end or unblock a session.

fieldtypedefaultwhat it does
idleMsnumber1800000No message and no agent activity for this long ends the session.
startupMsnumber60000How long the agent has to become ready before it is abandoned.
questionMsnumber300000How long a question posted to a thread waits for an answer.
abortMsnumber15000How long an abort waits for the agent before it is forced.