Skip to content

SOUL.yaml Reference

One SOUL.yaml file fully defines an agent (its identity, trigger, model, tools, memory, and behavior) and Soulacy hot-loads it the moment you save.

Quick Start

agents/assistant/SOUL.yaml
id: assistant
name: Assistant
description: General-purpose helper

trigger: channel
channels: [http]

llm:
  provider: openai
  model: gpt-4o-mini
  temperature: 0.2

system_prompt: |
  You are concise, helpful, and careful.

max_turns: 6
enabled: true

Validate any file before deploying:

sy agent validate path/to/SOUL.yaml          # human-readable
sy agent validate path/to/SOUL.yaml --json   # machine-readable

Annotated Complete Example

# ── Identity ──────────────────────────────────────────────
id: research-writer          # stable runtime ID: URLs, logs, agent__<id> tool names
name: Research Writer        # display name
description: Researches topics and writes a brief.   # shown to peer agents too
version: "1.0.0"
tags: [research, writing]
labels: { owner: ops }

# ── Trigger ───────────────────────────────────────────────
trigger: channel             # primary start mode: channel | cron | oneshot | webhook | internal | location
channels: [http, telegram]   # channel adapter IDs this agent serves
surfaces: [chat, telegram]   # optional UI/invocation surfaces; empty = derived

# ── Model ─────────────────────────────────────────────────
llm:
  provider: anthropic
  model: claude-sonnet-4-6
  temperature: 0.2
  max_tokens: 2048
  allowed_providers: [anthropic]   # refuse to run on any other provider
  tool_choice: auto                # auto | none | required | <tool name> (turn 1 only)

system_prompt: |
  Produce a short brief. Cite sources when available.

# ── Tools ─────────────────────────────────────────────────
tools:                       # agent-local Python tools, see Agent Tools page
  - name: summarize_csv
    description: Summarize a CSV file and return compact JSON.
    python_file: tools/summarize_csv.py
    timeout: 2m
    parameters:
      type: object
      properties:
        path: { type: string }
      required: [path]

builtins: [web_search, kb_search]  # restrict Go-native built-ins; [] = none; absent = default
knowledge: [company-docs]          # KBs searchable via kb_search
skills: [csv-analysis]             # Agent Skills (or ["*"] for all installed)
agents: [critic]                   # peer agents exposed as agent__critic
mcp_servers: [github]              # MCP allowlist by server
mcp_tools: [mcp__github__search_repositories]   # …or by full tool name
connections: [conn_0123456789abcdef] # explicitly granted authenticated website sessions
capabilities: [system]             # SEC-3: grant privileged OS tools (shell_exec, write_file, …)
system_tools: false                # legacy alias for capabilities: [system]
env: [GITHUB_TOKEN]                # SEC-5: extra host env var NAMES passed to tool subprocesses
confirm_tools: [write_file, shell_exec]   # pause for approval; ["*"] = all built-ins

# ── Memory ────────────────────────────────────────────────
memory:
  read_scopes: [agent, session]
  write_scopes: [agent]
  max_tokens: 2000

brain_memory:                # long-term memory layers
  episodic:   { enabled: true, max_inject: 5 }
  semantic:   { enabled: true, max_inject: 5 }
  procedural: { enabled: true, auto_update: false }

# ── Reasoning loop ────────────────────────────────────────
reasoning:
  strategy: react            # auto | react | plan_execute | flow | custom name
  max_steps: 6
  step_timeout: 30s
  total_timeout: 3m

# ── Schedule (cron / oneshot triggers) ────────────────────
schedule:
  cron: "0 8 * * *"
  run_missed_on_startup: true
  missed_startup_window: 24h
  output: { channel: telegram, to: "123456789", template: "{reply}" }

notify_on_failure:
  channel: telegram
  to: "123456789"
  include_error: true

security:
  passphrase: "change-me"

# ── Runtime ───────────────────────────────────────────────
max_turns: 10
stream_reply: true
enabled: true
run_timeout: 10m             # whole-run wall-clock cap (default 15m)

Identity

Field Type Notes
id string Required stable runtime ID. Avoid whitespace and path separators.
name string Human-readable display name.
description string Also shown to other agents when this agent is exposed as a peer tool.
version, tags, labels misc Optional metadata for packaging and filtering.

Trigger and Channels

trigger is the agent's primary start mode:

Trigger Meaning
channel Inbound channel traffic (HTTP, Telegram, Slack, Discord, WhatsApp).
cron Run on a cron schedule. Requires schedule.cron.
oneshot Run once at schedule.at.
webhook Activated by an HTTP POST to the agent's endpoint.
internal Callable by peer agents, not normally user-facing.
location Runs when a paired iPhone enters or leaves a place. Requires a location block (below).

channels binds the agent to channel adapter IDs. Platform credentials and inbound routing live in config.yaml: the agent only declares which adapters it serves.

surfaces controls where the same agent is visible or invokable. Leave it empty for Soulacy to derive a conservative default from trigger and channels; set it explicitly when one agent should serve multiple interfaces. This is the preferred way to make a scheduled agent interactive without duplicating it:

trigger: cron
channels: [telegram, slack]
surfaces: [schedule, chat, telegram, slack]
schedule:
  cron: "0 7 * * *"
  output: { channel: telegram, to: "@daily_brief" }

With that shape, the scheduler fires the agent, the Chat picker can test it, and mapped Telegram/Slack bots can route inbound messages to the same agent. The channel credentials and bot mappings still live under config.yaml.

Location trigger

A location agent asks every paired iPhone to watch one place. The phone does the geofencing with Core Location region monitoring, so nothing tracks the phone: the gateway learns only that the boundary was crossed, with the coordinate of that moment. The agent runs on the gateway and its reply is delivered to the phone that fired it.

trigger: location
location:
  name: Office            # shown on the phone; defaults to the agent name
  latitude: 47.6205
  longitude: -122.3493
  radius_m: 250           # 100-5000 metres
  on: enter               # enter | exit
  cooldown: 45m           # ignore repeat crossings for this long (default 30m)
  # device: <installation-id>   # optional: only this phone watches the place

On the phone, enable Settings → Location triggers and grant Always location access. iOS delivers region events even when the app is suspended. Each crossing appears as a trigger.location event in the workspace event stream and in the phone's Location triggers screen with its outcome.

LLM Block

Field Notes
provider / model Model selection. Empty provider falls back to llm.default_provider in config.yaml.
temperature, max_tokens Sampling temperature and output token cap.
base_url Per-agent override for OpenAI-compatible endpoints.
output_schema JSON Schema enforced on the final reply, with one repair re-prompt.
tool_choice First-turn tool policy: auto, none, required, or a specific tool name such as agent__researcher. Later turns are always auto.
allowed_providers Guard list; the engine refuses to run on any provider outside it. Recommended for cron agents on local models: allowed_providers: [ollama].

Tools, Skills, Knowledge, Peers

These are covered in depth on their own pages:

knowledge: lists knowledge base names this agent may search via the built-in kb_search tool. Empty means no KB catalog is injected at all.

connections: lists opaque authenticated-connection IDs selected in Studio. The runtime also requires a matching metadata grant for this agent, so copying an ID into SOUL.yaml cannot release another person's saved session.

Memory

memory controls classic session/cross-session memory injection (read_scopes, write_scopes, max_tokens).

brain_memory controls the long-term layers: see Agent Memory & Rulebooks:

Layer Keys Effect
episodic enabled, max_inject Injects past task records.
semantic enabled, max_inject Injects knowledge chunks.
procedural enabled, auto_update Injects the agent's rulebook; auto_update: true lets reasoning runs rewrite it (versioned, lockable).

Reasoning

The reasoning: block switches the agent from a single LLM call to a multi-step loop: see Reasoning Strategies:

Key Default Meaning
strategy (off) auto, react, plan_execute, flow, or a custom registered name.
max_steps 8 Hard ceiling for ReAct iterations.
max_plan_steps 6 Plan decomposition cap for plan_execute.
step_timeout 30s Per-step deadline.
total_timeout 180s Whole-task deadline.

The reasoning backend is derived automatically from llm.provider: there is nothing extra to configure.

Workflow

workflow: replaces the free-form LLM loop with a checkpointed pipeline. Two shapes are supported:

  • Linear steps (workflow.steps): see Workflow Steps.
  • Cyclic graphs (workflow.nodes / edges / entry / max_node_executions) with conditional routing and bounded loops: see Flow Graphs. When nodes are declared they take precedence over steps.

Schedule

Cron and one-shot agents use schedule:

trigger: cron
schedule:
  cron: "0 8 * * *"
  run_missed_on_startup: true     # catch up after host downtime
  missed_startup_window: 24h      # only replay fires within this window
  output:                         # publish successful runs to a channel
    channel: telegram
    to: "123456789"
    template: "{reply}"

When run_missed_on_startup is true, Soulacy replays at most one missed fire per startup. It chooses the latest one inside missed_startup_window (default 24h), so a long outage never floods downstream channels.

Use notify_on_failure to route errors somewhere a human will see them. Channel-triggered runs reply with errors automatically; cron and manual runs fail silently unless this block is set.

Reviewed learning and mission contracts

learning.enabled enables the private Notebook; learning.auto_propose allows proposals during eligible runs, not automatic approval. Follow the Notebook guide and exact learning reference.

mission holds a goal, typed acceptance checks, and bounded runtime limits. An absent allowed_tools preserves existing policy; an empty list denies all tools. See the mission example and release controls. Model preparation uses the selected saved model without weakening the mission; see model preparation.

Security and runtime controls

security.passphrase requires every new session to present the exact string before the agent answers anything; it is enforced in Go before the LLM runs, so no prompt injection can bypass it. security.passphrase_prompt customizes the challenge message.

Field Notes
max_turns Cap on LLM turns per run.
stream_reply Stream tokens to channels that support it.
enabled false parks the agent without deleting it.
run_timeout Whole-run wall-clock cap (5m, 30m, 1h; default 15m). Raise it for agents that call slow tools.

Tip

The GUI agent editor (Agents page) round-trips every field on this page, including the tri-state builtins modes and per-tool timeouts: you never have to hand-edit YAML unless you prefer to.