Skip to content

agent.json5 reference

One file per agent, at ~/.lionclaw/agents/<name>/agent.json5. JSON5, so comments, trailing commas and unquoted keys are allowed. Most people edit it through the dashboard's Agents tab; this section is the complete field list for when you edit it directly, or when an agent writes one for you.

${VAR} in a string is expanded from the environment when the file loads. Never put a secret in this file — put it in the vault and reference it by name (see Channels and connectors).

A minimal agent:

{
  name: "finance",
  description: "Watches revenue and flags anomalies",
  emoji: "📊",
  model: "olano:pro",
  system_prompt: "You are a finance analyst. Be precise with numbers.",
  tools: ["sql", "stripe"],
  channels: [{ type: "telegram", allowed_users: ["123456789"] }],
}

Identity

Field Type Default Meaning
name string required The agent's permanent handle. Lowercase letters, digits, dash and underscore, starting with a letter or digit. It is the folder its workspace lives in, the path segment in every /api/agents/… URL, and the exact string other agents use to reach it. It cannot be changed after the agent is created — a rename would strand its threads, memories, saved credentials and issue history. Put anything you may want to change later in display_name.
display_name string the handle What the agent is called, as opposed to how it is addressed. Never empty: leave it unset and it becomes the handle itself. Free text — spaces, capitals, accents and punctuation are all fine ("Merli the Analyst"). It appears on the agent's dashboard cards and chat header, in its own system prompt and whoami output, in the roster other agents read when choosing who to delegate to, and in the From: line of any email it sends. Empty falls back to name. Unlike name this can be edited at any time — nothing is keyed off it, so changing it keeps every thread, memory and issue intact.
org.title string | null the archetype's label, else null The agent's title — its position, what it is as opposed to what it is called ("Recruitment Specialist" for an agent named Hillary). Shown under the name on its cards, in the detail header, in the simple chat's header and profile, and on the org chart; told to the agent in its own prompt and whoami output, so it introduces itself by its name and describes its role by its title; and listed beside its name in the roster other agents read when delegating. Installing an archetype fills it with the archetype's human label unless you give one, so naming an install "Hillary" never costs her the role. Free text, optional, editable any time — Title on the Overview tab or the Talk to Agent tab. Empty means no position.
description string "" One line explaining what the agent is for. Shown in the roster and to other agents deciding whether to delegate to it.
emoji string "🦁" The agent's face wherever no avatar is set.
color string | null null CSS hex accent, e.g. "#c96442". Themes the dashboard when this agent is active.
avatar_url string | null null URL of a square image used as the agent's face. Falls back to emoji.
archetype string | null null The template this agent was created from. Informational.

Every place an agent is created asks for one name — the New agent dialog (the custom tab and the Team/Expert/Integration tabs alike) and the simple chat's New bot dialog — and that name is the display name. The handle is generated from it, never typed: the form shows what it will be under the name field (Hillary → hillary), and if that handle is already taken a number is appended (hillary-2). Installing a catalog archetype with a name of your own keeps the archetype's human label as the agent's title (so "Hillary" installed from the Recruitment Specialist archetype is Hillary, the Recruitment Specialist — and introduces herself that way); the New agent dialog prefills its Title field with that label so you can change it before installing. Installing an archetype without a name of your own keeps the archetype's label as the display name too (so a support archetype installs as "Customer Service Agent", not as its internal handle). Asking AgentFather to "install the Recruitment Specialist as Hillary" does the same thing.

Model and prompt

Field Type Default Meaning
model string default:turbo provider:model, e.g. anthropic:claude-sonnet-4-6, openai:gpt-5.5, an Olano tier like olano:pro — or a deployment default alias: default:turbo, default:pro, default:max. An alias follows whatever target is set in Config → Default models, so retargeting it there re-points every agent using it with no per-agent edits. New agents start on default:turbo, AgentFather included. See Models and credits.
system_prompt string "" The agent's core instructions. Merged with the workspace identity files.
reasoning_effort string | null null "low", "medium" or "high", for models that support it. Ignored otherwise.
model_kwargs object {} Extra parameters passed to the model (e.g. temperature).
prompt_mode "full" | "task" | "minimal" | "none" "full" How much scaffolding goes into the system prompt. full is the everyday value; the narrower modes suit single-purpose agents where a shorter prompt is cheaper and more focused.
vibes_preset "precise" | "warm" | "executive-brief" | "hacker-lab" "precise" Baseline tone. SOUL.md refines it.
default_verbosity "quiet" | "normal" | "verbose" "normal" How much of its work the agent narrates. quiet = the answer only; normal = the answer plus a note when a turn ran tools silently; verbose = full play-by-play. This is the starting point for new chats, not a ceiling — a user who runs /verbosity pins their own chat and that wins from then on.
prompt_cache object see below optimize_boundaries (default true) and sort_tools (default true). Leave both on unless you are debugging prompt construction.

A model string that will be rejected: olano:vision and the Olano image group are utility models the product calls on the agent's behalf. They cannot be an agent's brain, and setting one as model fails to load.

Tools

Field Type Default Meaning
tools string[] [] Toolkits and individual tools to enable, e.g. ["sql", "slack", "stripe"]. Listing a tool here binds it unconditionally.
disabled_tools string[] [] Remove specific tools that would otherwise be granted automatically. Use this to drop one tool without switching off a whole category.
default_internal_tools boolean true Master switch for every implicit grant — the universal tools, memory, notes, knowledge-graph, workspace context. Turning it off gives you a locked-down agent that only has what tools names.
top_level_memory_tools string[] | null null When set, an allow-list: only these memory tools survive. null means "all of them".
top_level_rag_tools string[] | null null Same, for document-search tools.
top_level_graph_tools string[] | null null Same, for knowledge-graph tools.
top_level_workspace_context boolean true Whether the agent can load extra workspace files on demand.
allowed_actions object {} { "action_name": "path/to/script" } — named scripts the agent may run via execute_action. Only bound when per-customer memory is on.

Every agent automatically gets a universal set you do not have to ask for: self-awareness (whoami), time (get_current_time), peer messaging (talk_to_agent), outbound notifications (notification_send_text / _photo / _document), chat directory lookups, the issue-tracker tools, web search and fetch, document readers, HTML rendering to PNG / PDF / SVG (render_html_image / render_html_pdf / render_html_svg — how charts, slides and social graphics are made, see the charts skill), and a recoverable trash can. Keep these unless you have a specific reason not to — several features assume they exist.

The knowledge-base tools are not in that set. kb_search, kb_list, kb_add_file, kb_add_url, kb_create_store, kb_sync, kb_delete and kb_remove_file bind only when tools names them. An agent without them cannot search your documents, and the dashboard's Knowledge tab cannot route through it. Add them to any agent you want answering from the document library.

Tools are grouped into these categories in the dashboard: Self & Time, Agent Mesh, User Interaction, Web & Documents, Multimodal, Housekeeping, Cortex, Memory, Customer Memory, Multi-user Actions & Scheduling, Knowledge (RAG), Knowledge Graph, Notes, Workspace, Agent Loop, Filesystem & Shell — plus 80+ service toolkits (Slack, Notion, Stripe, SQL, Google, GitHub, HubSpot, Salesforce, Shopify, Jira, Linear, and many more).

Multimodal tools are automatic when they can work. image_describe, image_generate, image_edit, text_to_speech and audio_to_text bind whenever the credentials to run them resolve. On a managed deployment everything except text_to_speech runs on Olano credits with no configuration at all. On an unmanaged install they appear once you set the matching provider key. text_to_speech is the one deliberate exception on every install: voice replies stay off until you pick a text-to-speech provider under Config → Speech (on a managed deployment choose Olano there to run it on credits).

Rendering HTML to an image, a PDF or an SVG. The render tools turn HTML and CSS into a PNG, a PDF or a vector SVG at exact pixel dimensions, using the browser that ships with every install — no API key, no network, nothing billed. They are on for every agent (switch them off with disabled_tools).

Tool What it does
render_html_image An HTML document or .html file → a PNG at exactly the width and height you ask for. scale: 2 exports at twice the pixel size for retina.
render_html_pdf The same, to a PDF, one page per break-after: page block — reports, printable documents, and what a LinkedIn native document (carousel) post needs.
render_html_svg Saves one <svg> of the rendered page as a standalone vector file — a chart that scales in a slide deck or a design tool.

All three take wait_for, a CSS selector to wait for before capturing, for pages that draw with JavaScript (the charts skill's pages add .render-done when finished). The tools pair with image_generate: the image model paints the background, and anything a person is meant to read — headline, statistic, logo, call to action — is set in CSS, so it comes out correct every time and a copy change is a re-render rather than a new image. Each accepts an inline HTML document or a path to an .html file, and writes to workspace/renders/ unless you pass save_path. Data charts go through the charts skill, which builds the page (recipes, or any Vega-Lite spec, in the clean / midnight / editorial / brand themes) and renders it with these tools; no plotting library is ever installed.

Private repositories. Local git — status, add, commit, diff, checkout, log — runs in the agent's own shell and needs no credential. The verbs that talk to GitHub go through the git toolkit, which runs them with the connected GitHub account and lands the files in the agent's workspace; the credential itself never enters the agent's sandbox, so a printenv there shows nothing before, during or after.

Tool What it does
git_clone(url, dest, branch, depth) Clone a repository into the workspace (dest relative to it; branch and a shallow depth optional).
git_fetch(repo_path, remote) Fetch a remote of a repository in the workspace.
git_pull(repo_path, remote, branch) Pull into a checkout in the workspace.
git_push(repo_path, remote, branch, set_upstream) Push a branch; set_upstream for a new one. Always raises an approval card. Force-pushing is refused.

HTTPS URLs only — an SSH URL is refused with the HTTPS form to use instead. The account comes from whatever way GitHub was connected (Connections → GitHub, a sign-in code, a magic link from the chat, or a GITHUB_TOKEN in the Vault); a missing one answers [missing-credentials] and names the link to send, never a request to paste a token. Add "git" to tools — or ask the agent, which adds it alongside the GitHub toolkit when you ask it to connect GitHub.

Bookkeeping. The quickbooks toolkit works a connected QuickBooks Online company across the four areas Olano's Intuit app is registered for. Connect it under Connections → QuickBooks (the environment field picks a sandbox company instead of the live one), then add "quickbooks" to tools — or use the built-in QuickBooksAgent subagent, which carries the whole set.

Area Tools
Customers quickbooks_list_customers, quickbooks_search_customers, quickbooks_get_customer, quickbooks_create_customer, quickbooks_update_customer
Invoicing & payments quickbooks_create_invoice, quickbooks_list_invoices, quickbooks_get_invoice, quickbooks_send_invoice, quickbooks_void_invoice, quickbooks_list_estimates, quickbooks_create_estimate, quickbooks_list_sales_receipts, quickbooks_create_sales_receipt, quickbooks_list_payments, quickbooks_record_payment
Expenses quickbooks_list_vendors, quickbooks_create_vendor, quickbooks_list_bills, quickbooks_create_bill, quickbooks_pay_bill, quickbooks_list_expenses, quickbooks_create_expense
Business insights quickbooks_profit_and_loss, quickbooks_balance_sheet, quickbooks_cash_flow, quickbooks_ar_aging, quickbooks_ap_aging, quickbooks_sales_by_customer, quickbooks_expenses_by_vendor, quickbooks_trial_balance

Three behaviours are worth knowing before you ask an agent to post anything:

  • Writes need the right reference id, and there are tools to find them. quickbooks_list_accounts returns the chart of accounts (filter it by type, e.g. Expense) — a bill or an expense needs one to categorise the money, and paying a bill needs the bank or credit-card account it comes out of. quickbooks_list_items returns the products and services an invoice line can point at. An invoice left without an item id falls back to the company's first active product/service rather than failing.
  • Invoices can carry many lines. Pass line_items a JSON array — [{"amount": 100, "description": "Design"}, {"quantity": 2, "unit_price": 50}] — and it replaces the single amount/description pair. Estimates and sales receipts take the same shape; bills and expenses take the same shape with an account_id per line.
  • Invoices are voided, never deleted. quickbooks_void_invoice keeps the document on the books at zero value so the audit trail survives, which is what an accountant expects. Updates change only the fields you pass; everything else on the record is left alone.

Reports come back as a readable outline rather than raw data, so an agent can quote a figure straight into a chat message or feed one into the charts skill.

Microsoft 365. The microsoft365 toolkit works a connected Microsoft 365 account: Outlook mail and calendar, contacts, To Do, OneDrive and SharePoint files, Excel workbooks, and Teams. Connect it under Connections → Microsoft 365 (or ask the agent for a sign-in link), then add "microsoft365" to tools, or use the built-in Microsoft365Agent subagent, which carries the whole set.

Area Tools
Mail ms365_list_messages, ms365_get_message, ms365_send_mail, ms365_create_draft, ms365_update_draft, ms365_send_draft, ms365_reply, ms365_forward, ms365_update_message, ms365_move_message, ms365_list_mail_folders, ms365_download_attachment
Calendar ms365_list_events, ms365_get_event, ms365_create_event, ms365_update_event, ms365_delete_event, ms365_respond_to_event, ms365_find_meeting_times, ms365_get_availability, ms365_list_calendars
People & mailbox ms365_find_people, ms365_get_profile, ms365_list_contacts, ms365_create_contact, ms365_set_auto_reply
Files ms365_list_drive_items, ms365_search_drive, ms365_search_files, ms365_upload_drive_file, ms365_download_drive_file, ms365_create_folder, ms365_share_drive_item, ms365_move_drive_item, ms365_delete_drive_item, ms365_find_sites
Excel ms365_excel_list_sheets, ms365_excel_read_range, ms365_excel_write_range, ms365_excel_add_rows
Teams ms365_list_teams, ms365_list_channels, ms365_send_channel_message, ms365_list_chats, ms365_read_chat, ms365_send_chat_message
To Do ms365_list_task_lists, ms365_list_tasks, ms365_create_task, ms365_update_task

What to know before asking an agent to act on it:

  • Drafts are the safe default. ms365_create_draft saves a message (or a reply or forward that keeps the thread) in Outlook's Drafts folder and returns a link to review it there; nothing leaves until ms365_send_draft, or until you send it yourself from Outlook. Ask for "a draft" when you want to read it first.
  • Meetings send real invitations. ms365_create_event with attendees makes Outlook email each of them an invitation, and online_meeting adds a Teams join link. Changing the time or the attendee list sends an update, and deleting a meeting you organize sends a cancellation with your note. When you name people rather than addresses, the agent looks them up first (ms365_find_people searches the people you work with, your organization's directory and your contacts), and it can ask Outlook for a time that suits everyone (ms365_find_meeting_times) before booking.
  • Times follow your mailbox. An agent reads and writes times in the time zone set in your Outlook settings unless you name another one.
  • Anything that reaches other people asks first under the safe and production security profiles: sending, replying and forwarding mail, sending a draft, creating, changing or cancelling a meeting, answering an invitation, turning on an automatic reply, uploading, sharing or deleting a file, writing to a workbook, and posting to Teams. Drafts, flags, folder moves, contacts and To Do tasks do not.
  • Files land in the workspace. Attachments and downloaded files are saved in the agent's downloads folder, where its other tools can open them; a download can also be converted to PDF on the way.
  • Some features need a work or school account. Teams, meeting-time suggestions and availability, the organization directory, SharePoint, search across shared files, and Excel are not available on a personal Microsoft account; mail, calendar, contacts, OneDrive files and To Do are.
  • A connection made before these features existed needs one fresh sign-in. It keeps working for what it could already do, and anything newer (drafts, mail folders, availability, people search, contacts, To Do, SharePoint and shared files, Teams chats) answers that the permission is missing; the agent then offers you a sign-in link. Signing in again replaces the connection in place. If your organization requires an administrator to approve app permissions, the administrator approves the new ones once.

Gmail (the google toolkit) works the same way for evidence and files:

  • Each message shows two times. Date is what the sender's mail program wrote; Received (UTC) is Gmail's own record of when the message arrived or was sent. Cite the second when the time matters, for example when an order was placed.
  • Attachments save into the workspace. A message lists its attachments by name, size and type, and gmail_download_attachments saves them (all of them, or the names you pick) into downloads/gmail/<message id>/ unless you name another folder inside the workspace. Saving the same file twice reuses the copy already there. Saving needs no approval: the file stays inside the agent's own workspace.
  • Mail written only in HTML (common for broker and bank notifications) is read as plain text instead of coming back empty.
  • New mail can wake the agent. See gmail_watch.

Subagents

Field Type Default Meaning
subagents object[] [] Specialists this agent can delegate to.
builtin_subagents list or object ["ops"] Ready-made subagents. "all", "none", or a list of names.

Each entry in subagents:

Field Type Default Meaning
name string required How the parent addresses it.
description string required What it is for — the parent reads this to decide when to delegate.
system_prompt string required Its instructions.
model string | null null Inherits the parent's model when unset. Use a cheaper model here for narrow work.
tools string[] [] Its own tools.
skills string[] [] Its own skills.
subagents object[] [] Nested specialists.
enabled boolean true
from_archetype string | null null Build this subagent from a ready-made archetype instead of writing it out.
system_prompt_extension string | null null Text appended to the subagent's prompt at build time, under an "Additional owner instructions" heading. Made for catalog-sourced subagents: the catalog prompt stays live, your additions ride on top.
backend object | null null Give the subagent its own filesystem/sandbox.

Subagents are internal to their parent — they are not agents in the roster, they have no channels of their own, and nobody outside can message them. Use a subagent for a specialty the parent needs; use a separate top-level agent when the thing needs its own inbox, schedule or memory.

Customizing a catalog-sourced member. A from_archetype entry (and a builtin_subagents integration) takes overrides next to the reference — any field you set wins over what the catalog ships, everything else keeps tracking the catalog:

subagents: [
  { from_archetype: "sales-coach" },                          // all catalog defaults
  { from_archetype: "sales-engineer", model: "default:pro" }, // stronger brain, same prompt
  {
    from_archetype: "sales-pipeline-analyst",
    system_prompt_extension: "Report figures in EUR. Our quarter ends in February.",
  },
]

In the dashboard this is the pencil on any subagent card (Subagents tab): pick a model ("Inherit" keeps the member's own default), read the base prompt, type additions into Additional instructions, or — deliberately, behind a warning — detach and replace the whole prompt, which freezes it against catalog updates. Team installs offer the same model choice per member in the New agent dialog before anything is created.

org — who may talk to whom

Covered in depth in Agent-to-agent and the org chart.

Field Type Default Meaning
title string | null the archetype's label The agent's position — shown under its name on cards, in the detail and chat headers and on the org chart, and told to the agent so it introduces itself by name and describes its role by title. Editable from the Title field on the Overview tab as well as here; see Identity.
manager string | null null The agent this one reports to.
reports string[] [] Its direct reports.
coworkers string[] [] Peers at a similar level. Display only.
input_agents string[] [] Agents allowed to message this one. Empty means no restriction.
output_agents string[] [] Agents this one may message. Empty means no restriction.
input_mode "everyone" | "none" | "list" | null null Explicit inbound policy. null infers from the list.
output_mode "everyone" | "none" | "list" | null null Explicit outbound policy.
mutating_from_manager_only boolean false When true, only the manager may trigger actions that change things; other peers get read-only service.
autogenerate boolean false Derive both allow-lists from the hierarchy instead of maintaining them by hand.

Two rules worth committing to memory: an empty list means unrestricted, not "nobody"; and an agent's manager and direct reports can always exchange messages, whatever the allow-lists say, so declaring a reporting line is enough to make delegation work.

agent_visibility — who this agent knows about

Field Type Default Meaning
mode "none" | "all" | "list" "none" Which other agents this one is told exist.
agents string[] [] The list, when mode: "list".

Visibility is awareness, not permission. It controls what the agent is told about its colleagues; org controls what it is allowed to do. An agent that can see a peer but has no org permission still cannot message it.

Access and lifecycle

Field Type Default Meaning
human_only boolean false Refuse all inbound agent-to-agent messages. Use for an agent that only ever takes direction from a person — the deployment operator, for example.
hidden boolean false An internal/test agent: not booted, and excluded from every listing — the dashboard roster, the org chart, admin and access-management views included.
external_access object all off Whether systems outside the deployment may send this agent messages, over MCP and/or A2A, and the public description they see. Edited on the agent's External access tab; see external_access and Beyond the deployment: A2A and MCP.
skills string[] [] Extra directories to load skills from, absolute or relative to the workspace. The agent's own workspace/skills/ and the shared common skills are always included and do not need listing here.
disabled_skills string[] [] Skills this agent must not load, by skill name (the name: at the top of its SKILL.md, which is also its folder name) — e.g. ["canvas-design", "xlsx"]. One name per entry; matching ignores capitalisation. A named skill is not described in the agent's prompt, cannot be delegated to, and is ignored by self-learning, but it stays on disk and stays listed (marked Off), so it can be switched back on. Naming a common skill takes it away from this agent only — every other agent keeps it, which is the reason this is a denylist rather than a deletion. An entry naming a skill that is not installed is harmless and does nothing. Empty (the default) means every discovered skill is loaded. Editable in the dashboard: Agents → agent → Skills, the switch on each row. Applies after a restart.
workspace string | null null Derived automatically. Do not set this by hand.
env object {} Per-agent environment values. Non-secret settings only.

mcp_servers — external tool servers

See MCP servers for the full walkthrough.

Field Type Default Meaning
url string required The server endpoint.
transport "sse" | "http" | "stdio" "sse" How to reach it. stdio runs the server as a process instead of connecting over the network — on managed deployments that process runs inside an isolated sandbox container.
enabled boolean true
command string | null null For stdio: the executable to run.
args string[] [] For stdio: its arguments.
env object {} For stdio: environment for the child process. Values may reference a stored credential as ${vault:NAME}.
headers object {} Extra HTTP headers — the usual way to pass an API key to an http/sse server. Values may reference a stored credential as ${vault:NAME}.
auth "none" | "oauth" "none" Use oauth for servers that require a sign-in.
oauth_scope string | null null Requested scope.
oauth_client_id string | null null For servers that need a pre-registered client.
oauth_client_secret string | null null Prefer the vault over writing this here.
disabled_tools string[] [] Hide specific tools the server offers. A hidden tool is never offered to the model; only the enabled ones are bound and listed in its prompt. The agent can still see what is hidden through self_list_mcp_tools and suggest enabling one when a task needs it.

remote_agents — external agents over A2A

Agents hosted outside this deployment that this agent may hand work to, over the open Agent2Agent (A2A) protocol. See Beyond the deployment: A2A and MCP for the walkthrough. Listing at least one enabled entry gives the agent the a2a_list_agents, a2a_discover_agent, a2a_send_message and a2a_get_task tools; an empty list (the default) means it can reach no external agent at all.

Field Type Default Meaning
name string required The handle the agent uses, e.g. acme-support. Letters, digits, -, _; matching ignores capitalisation.
url string required Base URL of the remote agent, e.g. https://agents.acme.com/support. Its Agent Card is read from <url>/.well-known/agent-card.json. Must be https://; hosts on private, link-local or cloud-metadata addresses are always refused.
enabled boolean true Off keeps the entry but removes the agent from reach.
description string "" What the remote agent is for, shown to your agent when it lists them. Empty falls back to the remote card's own description.
auth "none" | "bearer" | "api_key" "none" How to authenticate. bearer sends Authorization: Bearer <credential>; api_key sends the credential in the header named by api_key_header.
credential string "" The secret for bearer/api_key, ideally a vault reference such as ${vault:ACME_A2A_TOKEN} so it never sits in this file. Resolved per agent (this agent's override, then the shared entry).
api_key_header string "X-API-Key" Header name for api_key mode.
timeout_seconds integer 120 How long one message waits for an answer (5–900). A task still running at the deadline comes back as in progress with an id to poll.
allow_insecure_http boolean false Permit http:// for a loopback host only (local development). Never for anything on a network.
same_origin_endpoint boolean true Require the service endpoint the remote card advertises to share scheme, host and port with url, so a compromised card cannot redirect your credential elsewhere.

external_access — letting outside systems reach this agent

The inbound direction: someone else sending this agent messages. Not to be confused with mcp_servers (servers this agent uses) or remote_agents (agents this agent calls). Edited on the agent's External access tab; the matching home-wide switches live under Config → External access (external_access: — letting outside systems reach your agents). Both flags are off by default; the walkthrough is Beyond the deployment: A2A and MCP.

Field Type Default Meaning
mcp boolean false Publish the agent as an ask_<agent> tool on the deployment's MCP endpoint for platforms that speak the Model Context Protocol (Claude, ChatGPT, Cursor, agent frameworks). Needs the home-wide MCP switch too. Applies on the agent's next restart.
a2a boolean false Publish an Agent Card and JSON-RPC endpoint for agents and platforms that speak the Agent2Agent protocol. Needs the home-wide A2A switch too. Applies on the next restart.
description string "" Required while either flag is on. One to three plain sentences on what the agent can do for an outside caller, what it needs and what it will not do — the MCP tool description and the A2A card description, i.e. everything a remote caller sees. Written for a stranger; it is not the system prompt and is never shown to the agent itself. Up to 2000 characters. A config that turns a flag on without it is refused.

channels — messaging connectors

A list of objects, each with a type plus that connector's own settings. Connector fields vary widely and each is documented in the dashboard's Connectors tab. Credentials are never written here — see Channels and connectors.

channels: [
  { type: "telegram", allowed_users: ["123456789"] },
  { type: "slack", channels: ["#ops"] },
]

Supported types: telegram, slack, discord, whatsapp, whatsapp_twilio, whatsapp_meta_api, signal, email / imap, matrix, mattermost, sms, homeassistant, dingtalk, feishu, wecom, weixin, qqbot, bluebubbles, debox, api_server, webhook.

schedule — recurring work

See Scheduler and heartbeat.

Field Type Default Meaning
cron string | null null Standard 5-field cron, e.g. "0 9 * * 1-5". Shorthand for the five fields below.
minute / hour / day / month / day_of_week string "*" The fields, when you are not using cron.
timezone string | null null IANA zone, e.g. "America/Costa_Rica". Defaults to the deployment's zone.
message string "" What to send the agent when the job fires. Required for a normal job.
name string | null null A label for the dashboard.
thread_id string | null null Run in a specific conversation thread.
enabled boolean true
for_each_customer boolean false Fan the job out: run once per end user. Requires multi-user mode.
action string | null null Run a named entry from allowed_actions instead of sending a message. Only valid with for_each_customer.

In fan-out mode the message may use {customer_key}, {platform} and {user_id} placeholders.

heartbeat and issues — inherit-by-default blocks

Both use the same three-state pattern: null (or the key omitted) means inherit the deployment default from config.yaml; an explicit value overrides it for this agent only.

heartbeat:

Field Type Default Meaning
enabled boolean | null null (inherit; on) Whether the agent wakes up periodically to check HEARTBEAT.md.
interval_minutes integer | null null (inherit; 60) How often. Clamped to 1–1440.

issues:

Field Type Default Meaning
auto_process boolean | null null (inherit; on) Whether the agent picks up issues assigned to it on its own. false = manual: issues wait for a person to prompt the agent.
poll_minutes integer | null null (inherit; 15) How often it checks. Clamped to 1–1440.

context_window — how this agent's conversations are bounded inside its model's context window. Every field is null = inherit the home-wide context_window: block in context_window: — compaction and oversized tool results, which also explains what each one does:

Field Type Default Meaning
keep_fraction number | null null (inherit; 0.35) Share of the window kept verbatim after a compaction, when the window is known. 0.05–0.8.
keep_tokens integer | null null (inherit; 40000) The same budget in tokens, used when the window is unknown. 2000–2000000.
trigger_fraction number | null null (inherit; 0.85) When automatic compaction fires, as a share of the window. 0.3–0.98.
trigger_tokens integer | null null (inherit; 170000) The same trigger in tokens, when the window is unknown. 8000–4000000.
mcp_result_token_limit integer | null null (inherit; 5000) MCP results larger than this are saved to a workspace file and replaced by a preview. 0 = no cap.

gmail_watch: wake on new email

Starts a turn for the agent when new mail matching a Gmail search reaches its connected Gmail. Off by default. Set it on the agent's Overview tab, in the Gmail watch card, which also shows the agent's Google connection and the watch's last check; AgentFather can set it too. Changes apply immediately, no restart.

It needs the agent's Google connection. The watch reads the mailbox through the same Google sign-in the Gmail tools use (Connections, per agent or shared). Without one it reads nothing: the card shows Not connected with a Connect Google button, and the bell raises one notification that clears itself on the first check after you connect. Mail that arrived before the connection is not replayed. If Google refuses the saved sign-in later, the card and the bell say so the same way. If the mailbox cannot be connected to Google at all, add it on the Connectors tab as an Email connector instead: it checks the mailbox over IMAP with an app password and wakes the agent for every new message, without a search filter.

Field Type Default Meaning
enabled boolean false Turns the watch on. The agent needs the Google connection (Connections) with Gmail access.
query string "" (watches in:inbox) A Gmail search, typed exactly as in Gmail's search box, for example from:(@broker.example) has:attachment.
poll_minutes integer 5 How often Gmail is checked, 1 to 1440. New mail is picked up within this many minutes.
prompt string "" What the agent should do with the new messages. Empty tells it to read each one, save the attachments it needs and act on its instructions.
max_per_check integer 10 At most this many messages go into one turn, oldest first, 1 to 50. The rest wait for the next check.
gmail_watch: {
  enabled: true,
  query: "from:(@broker.example) has:attachment",
  poll_minutes: 5,
  prompt: "Match each confirmation to its Trade ID and record the execution evidence.",
}

How it behaves:

  • Switching it on never replays your inbox. The first check only records the time; mail that arrives after it is delivered. Changing query starts over the same way.
  • One message wakes the agent once. The ids already delivered are kept in the workspace, so a restart or a busy inbox never hands the same message over twice.
  • A failed turn is retried. If the agent's turn fails, the same messages are offered again on the next check, up to three times, after which they are skipped so one bad message cannot wake the agent forever.
  • Email is treated as a stranger's words. The turn starts with the list of new messages (sender, subject, received time, ids) and the agent reads them with its Gmail tools. Low-risk actions go ahead; anything that needs approval waits in the dashboard's Approvals list, whatever the agent's security profile says about scheduled jobs. The turn cannot change the agent's own settings or use owner-only tools.
  • These runs appear in the agent's chat list with its other scheduled runs.

Gmail can also push new mail instantly, but only through a Google Cloud Pub/Sub topic you would have to create and authorize. The watch avoids that setup at the cost of a few minutes' delay.

webhooks

See Webhooks for examples of both directions.

Field Type Default Meaning
name string required A label.
direction "outbound" | "inbound" "outbound"
enabled boolean true
url string "" Outbound: where to POST. Required for outbound, must be http(s).
events string[] ["*"] Outbound: which events to send.
secret string | null null Outbound: HMAC signing secret. Supports ${VAR}. Never returned by the API.
headers object {} Outbound: extra headers.
timeout_seconds number 10.0
max_retries integer 2
rate_limit_per_minute integer 60 0 = unlimited.
verify_tls boolean true
block_private_addresses boolean true Refuse to deliver to internal addresses. Leave on.
prompt string "" Inbound: what the agent should do with each payload.
respond boolean false Inbound: run synchronously and return the agent's reply in the HTTP response.
accumulate_thread boolean false Inbound: one persistent conversation for all payloads, rather than a fresh one each time.
max_body_bytes integer 256000 Inbound: reject larger payloads.

Outbound event names: message.received, message.sent, agent.started, agent.stopped, agent.error, task.scheduled, task.completed, tool.called, approval.requested, budget.exceeded, or * for all.

memory

Field Type Default Meaning
markdown boolean true Keep MEMORY.md and daily logs.
rag boolean false Also index memory for meaning-based search.
rag_collection string "lionclaw_memory" Where indexed memory is stored.
self_learning boolean true Distil conversations into durable memory in the background.
self_learning_interval integer 3600 Seconds between distillation passes.
per_customer boolean false Give every end user their own isolated memory. Turns on multi-user mode (see Multi-user: two different features).

customer_service — per-end-user isolation

For a shared agent that many people talk to. See Multi-user: two different features.

Field Type Default Meaning
enabled boolean false Turn on per-end-user isolation. Mirrors memory.per_customer.
prompt_extension string "" Extra instructions for handling many users. Blank uses a sensible default.
user_scheduling boolean false Let end users schedule their own reminders.
max_jobs_per_user integer 3 Cap per user.
min_job_interval_minutes integer 60 Minimum spacing between a user's jobs.
connections boolean false Let end users connect to each other, with consent.
max_connections_per_user integer 25
max_connection_sends_per_day integer 20
connection_invite_ttl_hours integer 168 How long an invite stays valid.
connection_contact_info string "" Shown to users in connection flows.
user_credentials boolean false Let end users store their own credentials for the agent to use on their behalf.
customer_credential_names string[] [] The allow-list of credential names they may store.

user_scheduling, connections, user_credentials and per-customer schedule fan-out all require multi-user mode; setting one without it makes the config fail to load rather than silently doing nothing.

message_policy — message length caps and silence rules

For an agent whose chat address is public. Everything here is off unless you set it, so an existing agent is unaffected. Edit it in Agents → your agent → Multi-user → Message policy.

Field Type Default Meaning
max_input_chars integer | null null Longest inbound message accepted, in characters of the sender's own text. Checked before any model call, so an over-long message costs nothing. null = unlimited.
on_oversize "reject" | "truncate" | "ignore" "reject" What happens past that limit. reject sends a short notice and runs no turn; truncate trims to the limit and answers the shortened text; ignore drops it with no reply. Only applies while max_input_chars is set.
oversize_notice string "" Your wording for the reject notice. Blank uses the built-in line, which names both the limit and the length actually sent.
ignore_patterns string[] [] Regular expressions. A message matching any of them is dropped before any model call — free and exact. Matching is case-insensitive and unanchored; use ^…$ to require the whole message. An invalid expression makes the config fail to load rather than silently never matching. Keep them simple: a pattern with nested repeats ((a+)+) can take seconds per message, and only the first 20,000 characters of a message are scanned.
ignore_rules string[] [] Plain-language conditions the model judges, e.g. "The message is not written in English". Added to the system prompt; the agent replies with a silence marker and the runtime sends nothing. Handles what a regex cannot — language, tone, intent — at the cost of one model turn per ignored message.
ignore_notice string "" What an ignored sender sees. Blank = true silence (nothing is sent). Text = that one line instead. Applies to patterns, rules, and on_oversize: "ignore".
max_output_tokens integer | null null Hard ceiling on one reply, translated to whichever parameter your provider uses. null = the provider's own maximum.

Four things worth knowing before you rely on these:

  • Slash commands are never screened. /new, /help, /model and the rest pass through whatever you set, so a tight limit or a broad pattern cannot lock you out of your own bot.
  • Only external conversations are screened — messenger channels, dashboard and REST chat, and the OpenAI-compatible API. Scheduled runs, heartbeats and background thinking are not, so a capped support desk can still write a long nightly report.
  • A capped reply stops mid-sentence and is not continued. Without a cap, Olano automatically asks the model to carry on when a reply is cut short; with one, that retry is switched off, because otherwise every capped reply would cost several extra turns — the opposite of what a cap is for.
  • On reasoning models the output ceiling counts thinking as well as the answer, so a low value can return an empty reply. Keep it above ~1024 there, or set reasoning_effort to none/low.

Patterns and rules are the cheap and the flexible halves of the same job. Anything a regular expression can already decide belongs in ignore_patterns, where it costs nothing; ignore_rules is for the judgements only a model can make. Pairing rules with max_input_chars and max_output_tokens keeps the turn they cost small.

message_policy: {
  max_input_chars: 500,
  on_oversize: "reject",
  ignore_patterns: ["^\\W*$", "\\b(buy|cheap) followers\\b"],
  ignore_rules: [
    "The message is not written in English",
    "The message is nonsense or just testing the bot",
  ],
  ignore_notice: "",          // stay completely silent
  max_output_tokens: 300,
}

monetization

See Subscriptions and monetization.

Field Type Default Meaning
enabled boolean false
providers "stripe" | "telegram_stars" list [] How people pay.
trial_days integer 3 Free trial length.
channels string[] [] Which channels the paywall applies to.
gate_groups boolean false Also require a subscription in group chats.
plan.price_cents integer 500 Display amount.
plan.currency string "usd"
plan.interval "month" | "year" "month"
plan.stripe_price_id string "" Empty means Stripe is off.
plan.stars_amount integer 0 0 means Telegram Stars is off.
plan.product_name string "Subscription"
plan.offer_copy string see default The pitch shown to users.
plan.success_url / plan.cancel_url string "" Where checkout returns to.

Payment provider secrets go in the vault, never in this block.

security

See Privacy and security for what each control actually does.

Field Type Default Meaning
profile "safe" | "developer" | "production" "developer" Preset that fills in the unset fields below.
trust_level integer 0–4 2 0 observer, 1 assistant, 2 collaborator, 3 autonomous, 4 developer.
hitl_enabled boolean false Require human approval before risky actions.
hitl_shell boolean false Approve every shell command.
hitl_write_tools boolean false Approve every tool that changes something.
hitl_auto_approve_low_risk boolean true Let clearly-safe actions through without asking.
hitl_timeout_seconds integer 300 How long a request waits before expiring.
pii_scan_input boolean true Scan what comes in for personal data.
pii_scan_output boolean true Scan what goes out.
pii_scan_peer boolean false Also scan agent-to-agent traffic.
pii_action "warn" | "redact" | "block" "warn" What to do on a hit.
input_scan boolean true Detect prompt-injection attempts in incoming text.
output_scan boolean true Screen outgoing text.
web_scan boolean false Also screen fetched web content.
shell_guard boolean true Block dangerous shell patterns.
skill_audit boolean true Audit skills before running them.
skill_audit_level "permissive" | "strict" | "paranoid" "strict"
credential_scrub boolean true Strip anything that looks like a secret from output.
ssrf_protection boolean true Block requests to internal network addresses.
ssrf_allowed_domains string[] [] The older spelling of the top-level egress_allow (below). Still honoured — the two are combined.
ssrf_blocked_domains string[] [] The older spelling of the top-level egress_deny. Still honoured — the two are combined.
rate_limits object see Privacy and security Per-tool and per-message caps.
tools object {} Fine-grained per-tool permissions.

Setting profile fills in any of trust_level, pii_action, input_scan, output_scan, hitl_enabled, hitl_auto_approve_low_risk and skill_audit_level you have not set yourself. Anything you set explicitly wins.

egress_allow / egress_deny — which internet destinations the agent may reach

Two top-level lists in agent.json5 (not under security), edited in the agent's Advanced tab:

egress_allow: ["api.github.com", "example.com"],   // empty = inherit the deployment's list
egress_deny:  ["pastebin.com"],                    // empty = add nothing
Field Type Default Meaning
egress_allow string[] [] Domains this agent's web and HTTP tools may reach. Empty inherits the deployment's list (Config → Outbound access); when that is empty too, every public destination is allowed. A non-empty list here replaces the deployment's allow list for this agent.
egress_deny string[] [] Domains this agent's tools must never reach. Added to the deployment's deny list — an agent can block more, never less.

Format. One bare domain per entry, like api.github.com. A domain covers its subdomains, so example.com also matches files.example.com. A leading *., an https:// prefix, a port or a path are stripped when the config loads; anything that is not a domain name or an IPv4 address fails the config to load, the way a bad sandbox.memory does. Deny is checked before allow, so a denied domain stays blocked even when it is also allowed.

What it governs. Everything the agent can reach the internet with:

  • its web and HTTP tools, the headless browser, and calls to remote agents;
  • every integration toolkit (Stripe, Salesforce, Slack and the rest), whichever service they talk to;
  • its sandbox shell — a curl, pip install, npm install or git clone inside a shell command.

A blocked destination comes back as a readable error (Host 'x' is not in the allowlist.) rather than a silent failure or a hang, so the agent can tell you what it could not reach. Internal and private-network addresses are blocked regardless of these lists (ssrf_protection), and your deployment can always reach its own control plane whatever you put in the lists — otherwise a typo could take a box offline with no way to tell you.

When it applies. The very next tool or shell command after you save — no restart, no container rebuild. An agent needs every service it actually uses on its allow list: a support agent that reads email and writes to your CRM needs both, and a missing entry shows up as that one integration failing. If an agent suddenly cannot install a package, check whether its allow list covers the package index it is downloading from.

One limit worth knowing. Confining the sandbox shell needs the host's firewall. On a deployment where that is unavailable — an unusual setup without the required privileges — the shell is still pointed at the policy and ordinary tools obey it, but a program written to ignore that setting could go around it. The runtime writes a warning at startup naming this, and the agent's tools are unaffected either way. Managed deployments always have it.

budget

Field Type Default Meaning
daily_usd / weekly_usd / monthly_usd number | null null Spending caps. null = no cap.
alert_at number 0.8 Warn at this fraction of the cap.
fallback_model string | null null Switch to a cheaper model when the cap is hit, instead of stopping.
hard_stop boolean false Stop the agent entirely at the cap.

pulse — proactive suggestions

Field Type Default Meaning
enabled boolean true
in_channels boolean false Push them to messaging channels.
email_digest boolean false Send a daily digest.
digest_time string "09:00" Local time for the digest.
quiet_hours_start / quiet_hours_end string "" e.g. "22:00" / "08:00". Empty = no quiet hours.
max_items_per_session integer 5
max_items_per_day integer 20
generated_items_per_cycle integer 3
pulse_item_ttl_hours number 168.0 How long a suggestion stays relevant.
min_turns_for_generation integer 4 Don't generate from very short conversations.
curation_cadence "session" | "daily" | "weekly" "daily"
curation_interval_hours number 24.0
focus_topics string[] [] Bias suggestions toward these.
sources string[] ["agent","user","approvals","ops"] Where suggestions may come from.

cognition — the Cortex engine, per agent

Every boolean here is three-state: null (or omitted) means inherit the deployment default. That includes the master enabled switch, so "off for the fleet, on for one agent" works.

Field Type Default Meaning
enabled boolean | null null (inherit; off) Master switch for this agent. null follows the deployment's cognition.enabled, which is off unless someone turned it on, so a new agent runs no Cortex. true runs Cortex for this agent even while the fleet is off; false keeps it off even while the fleet is on.
fast_model / deep_model string | null null (inherit) Models for light and heavy background work.
autonomous boolean | null null (inherit) Whether it acts on its own initiative.
auto_approve boolean | null null (inherit) Whether its own actions skip the approval queue.
intensity "relaxed" | "balanced" | "aggressive" | null null (inherit; relaxed) How much background work it does. null follows the deployment's cognition.intensity, "relaxed" unless changed.
approval_required string[] ["external_send","irreversible","identity_edit"] Categories that always need a human, whatever auto_approve says. identity_edit is any change to SOUL.md, AGENTS.md or IDENTITY.md; external_send is anything that sends something outside the deployment (email, calendar, posts, messages to other agents); irreversible is moving money or overwriting a stored secret. Applies both to the Fully Autonomous sweep of the approval queue and to tool calls made during a background run — a held item waits in Pulse → Approvals with its usual Approve / Reject buttons. Set it to [] for an agent whose owner wants everything unattended, or list only the categories to keep; unknown names are ignored.
self_learning, brainstorming, idea_generation, prototyping, skills_optimization, workspace_optimization, memory_optimization, pulse boolean | null null (inherit) Individual capabilities.
deep_self_learning boolean true Use the deep model for distillation.
memory_garden_cron string | null null (inherit) When to tidy memory (merge duplicate facts in MEMORY.md, roll up old daily logs; snapshotted and undoable from Pulse). Standard 5-field cron, e.g. "30 4 * * *". Leave it null to follow the deployment's intensity preset — once a day around 04:30 at every intensity, with the exact minute spread out per agent so a fleet does not all start at once. An explicit value is used exactly as written and is never spread.
pulse_agent_cron string | null null (inherit) When to generate Pulse suggestions. Standard 5-field cron. null follows the intensity preset (daily at 09:00 on relaxed, every 12 h on balanced, every 6 h on aggressive), spread per agent; an explicit value is used as written.
idea_expand_cron string | null null Reserved for scheduled idea research — currently has no effect. Ideas are expanded when you ask for it or approve a Pulse suggestion; leave this null.
workspace_optimize_cron string | null null (inherit) When the self-optimization pass over the agent's identity and behavior files runs (snapshotted and undoable from Pulse). Standard 5-field cron. null follows the intensity preset (Sundays at 04:00 on relaxed/balanced, every third day on aggressive), spread per agent; an explicit value is used as written.
skill_librarian_cron string | null null (inherit) When the learned-skill library is tidied — duplicates merged, survivors polished (snapshotted and undoable). Standard 5-field cron. null follows the intensity preset (Sundays at 04:30 on relaxed/balanced, every third day on aggressive), spread per agent; an explicit value is used as written.
max_deep_runs_per_day integer 12 Cost ceiling on heavy background work.
worker_max_iterations integer | null null (inherit) Step budget for each background reasoning run (Think phases, self-learning, the optimizers). Counted in internal steps — roughly 4 per round of thinking plus tool use, so the shipped default of 100 allows about 25 rounds per run. A run that exceeds it stops with a "hit its step limit" error and its work is discarded; raise this if that error keeps appearing, lower it to cap the cost of any single run. Accepts a whole number from 10 to 400; a value outside that range is pulled to the nearest end rather than rejected. Leave it unset (or null) to inherit the deployment-wide value set in Cortex → Autonomy under "All agents" — that is what most agents should do. Caps one run — how many runs happen per day is governed by intensity and the agent's budget — and a run also stops at worker_timeout_seconds, whichever comes first. Editable in the dashboard: Cortex → Autonomy → Step budget.
worker_timeout_seconds integer | null null (inherit) Wall-clock limit for each background reasoning run, in seconds. A run that exceeds it stops with a timeout and its partial work is discarded. Accepts a whole number from 30 to 7200; the shipped default is 1200 (20 minutes). Leave it unset (or null) to inherit the deployment-wide value. Raise it whenever you raise worker_max_iterations — a large step budget behind a small time limit simply fails on time instead, losing the same work under a different name. Editable in the dashboard: Cortex → Autonomy → Time limit.
force_tier "fast" | "deep" | null null Pin background work to one tier.
enable_memory_gardener, enable_pulse_agent, enable_idea_expansion, enable_skillsmith, enable_workspace_optimizer, enable_skill_librarian boolean true Individual background workers.

wallet — on-chain operations

Off unless you need it.

Field Type Default Meaning
enabled boolean false
mode "cold" | "hot" "cold" cold can read but not sign.
chains string[] ["ethereum"]
hot_mode_lock_minutes integer 30 Auto re-lock after this long.
lock_on_session_end boolean true
spending_limits object {} Per-chain caps.
require_approval "always" | "high_value_only" | "never" "always"

commands — slash commands

Field Type Default Meaning
enabled object {} Only the deviations from the defaults, e.g. { "model": false }.
custom object[] [] Your own commands.

Each custom command has name (lowercase, [a-z0-9][a-z0-9_-]*, must not collide with a built-in), prompt (supports {args}), and an optional description. This governs chat surfaces and the dashboard.

What you set here is also what the messengers advertise. Each connector's slash menu — Telegram's / autocomplete, Discord's slash-command list — carries exactly the built-ins this agent exposes plus its custom commands, and /help lists the same set. Edits apply immediately, without restarting the agent; a messenger may cache its own autocomplete for a minute (on Telegram, reopen the chat to refresh it), but typing a command always follows the current setting.

Two naming caveats for custom commands. Telegram's menu accepts only lowercase letters, digits and _, so a hyphenated /daily-report still works when typed but will not be listed there — name it daily_report to have it appear. Discord accepts hyphens, but skips a name longer than 32 characters.

shared_paths — sharing files between agents

Field Type Default Meaning
source string required The path on the host.
target string required Where it appears for the agent. Normalised under /shared/.
mode "ro" | "rw" "ro"
persistence "session" | "persistent" "persistent"
subjects object[] [{type:"agent",name:"self"}] Who gets it: {type: "agent"\|"subagent"\|"all_subagents", name}.

backend — where the agent's tools actually run

type What it is
local_shell (default) Real filesystem plus a shell, rooted at the agent's workspace. What you want almost always.
filesystem Files without a shell. Add access_mode: "read" for a read-only agent.
state In-memory only — no real files. Testing.
store A namespaced key-value store.
composite Route different paths to different backends.
lionclaw_docker Run the agent's tools in a container. Options: persistent, image, network ("none" | "bridge" | "host"), snapshot_on_stop, memory, cpus. See the note below before choosing it.
daytona, modal, runloop, agentcore, langsmith Hosted sandbox providers.

local_shell and filesystem take root_dir (default "workspace").

Choosing lionclaw_docker. Most agents should stay on local_shell: on a managed deployment it already runs in the isolated container, so naming the docker backend by hand buys nothing and costs you the shorthand the dashboard's Environment dropdown understands. Reach for it only when you want to control the container settings directly.

Whichever you pick, the container is only provided when the deployment has sandbox execution enabled. Without it the agent is downgraded to files-without-a-shell rather than being given a shell on the host — so execute and the file tools go away together. If an agent's file or shell tools stop working, check sandbox in the agent's diagnosis: it names the container, or the reason there isn't one.

On a managed deployment three of the options are pinned and your value is ignored (a warning says so at startup): network cannot be "host", image is the deployment's own sandbox image, and persistent is forced on. On your own self-hosted box all three are honoured as written.

mounts is accepted by the config but does nothing — what the container can see is the agent's workspace plus any shared paths you have configured. egress_allow and egress_deny on this block are the older home of the agent's outbound lists: they are still read, and combined with the top-level egress_allow / egress_deny described in security, but they govern the agent's web tools, not the sandbox shell — a curl inside execute is decided by network and your host firewall, not by these lists. Move them to the top level of agent.json5, which is where the dashboard edits them; a config that still sets them here logs a reminder at startup.

sandbox — this agent's share of the machine. A top-level block, separate from backend, that applies wherever this agent's shell runs in a sandbox:

sandbox: { memory: "8g", cpus: 4 }

memory is a size like 4g or 2048m (minimum 6m); cpus is a number of cores, 0.1–64, where 0.5 means half a core. Set one and the other still follows the deployment default, so raising an agent's memory does not quietly pin its CPU. Omit the block entirely and the agent inherits the deployment answer — normally 25% of the machine's RAM and 50% of its cores (see the sandbox key in the config.yaml reference). Use this for the one agent that runs heavy builds or data jobs; the limits are ceilings, so an agent that sits idle costs nothing regardless of what you set here. Changing a value rebuilds that agent's sandbox on its next start: its workspace survives, anything installed by hand inside the sandbox does not.

Runtime knobs

Field Type Default Meaning
acp_enabled boolean false Expose an HTTP endpoint so external code can drive this agent. Binds to localhost with no authentication — reach it over an SSH tunnel, never publish the port. For an authenticated remote surface use an inbound webhook instead.
acp_port integer 8765 Agents sharing a port share one server.
iteration_budget integer 0 Cap on reasoning steps per turn. 0 = no cap.
truncation_retry_limit integer 3 Retries when a reply is cut off.
tool_error_retry_limit integer 3 Circuit breaker for a repeatedly failing tool. 0 disables it.

What makes a config fail to load

Olano refuses to load a broken config rather than booting a half-configured agent. The common causes:

  • Legacy keys. sandbox_backend, a plain string backend:, or security.sandbox_mode / shell_use_docker / docker_image / docker_network. Use the backend object instead. sandbox is a live key again, but only as a block of resource limits — sandbox: true or sandbox: "docker" (the old on/off spelling) is still refused.
  • A utility model as a brain. olano:vision or the image group in model, cognition.fast_model or cognition.deep_model.
  • Multi-user features without multi-user mode. user_scheduling, connections, user_credentials, or a schedule entry with for_each_customer, when customer_service.enabled / memory.per_customer is off.
  • An invalid ignore pattern. A line in message_policy.ignore_patterns that is not a valid regular expression — caught on save, so a rule can never silently match nothing.
  • A dangling archetype reference. A from_archetype naming something that does not resolve.
  • A malformed custom command name, or one that collides with a built-in.
  • A schedule entry with neither message nor action, or action without for_each_customer.