Skip to content

Soul Files

Soul files are standalone YAML documents stored in custom/souls/. Each file defines one agent identity. There is no envelope or wrapper structure — the soul fields sit at the top level of the file.

  • Directorycustom/
    • Directorysouls/
      • researcher.yaml
      • editor.yaml
      • fetcher.yaml

The embedded id is the soul’s lookup key. When a workflow block sets soul_ref: researcher, the parser resolves the soul whose embedded id is researcher; external soul files must use the same filename stem, such as custom/souls/researcher.yaml.

Only .yaml files are discovered. Files with the .yml extension are ignored. Files whose names start with _ are not excluded — all .yaml files in the directory are loaded.

A soul file is flat YAML with no version field and no wrapping key. The file content maps directly to the soul definition:

custom/souls/researcher.yaml
id: researcher
kind: soul
name: Researcher
role: Senior Researcher
system_prompt: |
You are a senior research analyst. Given a topic, you produce
a structured report with findings, sources, and confidence levels.
Always cite your sources and flag low-confidence claims.
provider: openai
model_name: gpt-4o
temperature: 0.3
max_tokens: 4096
tools:
- http
max_tool_iterations: 5
avatar_color: accent

These fields must be present in every soul file. The parser raises a ValidationError if any are missing.

FieldTypeDescription
idstrEmbedded soul id. Must match the filename stem for external soul files.
kind"soul"Entity kind. Must be "soul".
namestrDisplay name for this soul.
rolestrThe agent’s role (e.g., “Senior Researcher”). Displayed as the soul name in the UI.
system_promptstrSystem instructions defining the agent’s behavior and constraints.
FieldTypeDefaultDescription
model_namestrNoneModel identifier (e.g., gpt-4o, claude-sonnet-4). Falls back to the runner’s default if not set.
providerstrNoneProvider for the model (e.g., openai, anthropic). Falls back to the runner’s default if not set.
temperaturefloatNoneSampling temperature override. When None, uses the model’s default.
max_tokensintNoneOutput token limit override. When None, uses the model’s default.
toolslist[str]NoneTool IDs this soul can use. Each must also be declared in the workflow’s top-level tools: section.
required_tool_callslist[str]NoneLLM-facing tool function names that must be called before the agent completes.
max_tool_iterationsint5Maximum number of tool-use iterations per execution.
avatar_colorstrNoneUI color for displaying the soul. Accepts one of six preset tokens: accent, info, success, warning, danger, neutral.

The id field is the identity. For external soul files, it must match the filename stem exactly. A file named researcher.yaml must contain id: researcher; a mismatch is rejected during discovery.

Workflow blocks reference the embedded id with soul_ref: researcher.

Soul files are validated using Pydantic’s model_validate. The schema model (SoulDef in schema.py) enforces:

  • extra="forbid" — unknown fields raise a validation error. Only the fields listed above are accepted.
  • Required fieldsid, kind, name, role, and system_prompt must be present. A missing field produces a Pydantic ValidationError.
  • Type checking — each field must match its declared type. A string where an integer is expected raises a validation error.

There are no ge, le, or min_length constraints on soul fields. The max_tool_iterations field accepts any integer.

When the parser encounters a soul_ref on a block, it resolves the reference through these steps:

  1. Discover external souls. The parser scans custom/souls/ for .yaml files. Each file is loaded, validated against the Soul schema, checked for id == filename stem, and stored in a dictionary keyed by embedded id.

  2. Merge inline souls. If the workflow YAML contains a souls: section, inline definitions are merged over the external map. When keys overlap, the inline soul wins and a warning is logged: "Inline soul 'X' overrides external soul file".

  3. Look up soul_ref. The block’s soul_ref value is looked up in the merged map. If not found, the parser raises a ValueError listing available souls.

A soul’s tools list declares which tools the agent needs, but every listed tool must also appear in the workflow’s top-level tools: section. If a soul references a tool not declared in the workflow, the parser raises a ValueError:

Soul 'fetcher' (custom/souls/fetcher.yaml) references undeclared tool 'http'.
Declared tools: []

This two-layer design gives workflow authors explicit control over which tools are available in a given workflow, regardless of what individual souls request.

The modified_at field is set by the API server when a soul is created or updated through the GUI. It stores a Unix timestamp (float). This field is not part of the engine’s runtime Soul model — it is metadata tracked by the API layer for display in the Soul Library’s “Modified” column.