Soul Artifacts¶
A soul is an AOS persona and identity document — a single-file SOUL.md that captures an agent's character, voice, operating principles, and system prompt. Souls are a tier-0 primitive: catalogable, versionable, and composable just like every other SkillMeat artifact (skills, commands, agents). This guide covers what a soul is, how to add, update, and list souls in your collection, how to associate a soul with an agent, and how to render a soul for a specific runtime using the skillmeat soul render pipeline.
Command surface consolidated — use the generic commands
As of v0.62, soul creation/update and soul↔agent association are handled by SkillMeat's generic artifact commands (add <type> --update and artifact link / artifact links) rather than soul-only commands. soul capture, soul associate, and soul list-associations are deprecated thin aliases that remain fully functional — see the "Deprecated" subsections under Registering or Updating a Soul and Associating a Soul with an Agent for the mapping and migration guidance. soul render itself is not deprecated; only its --target-profile flag was renamed to --profile (old flag still works as a hidden alias).
Overview¶
Souls are the identity layer of the Agentic OS. Where a skill describes a capability and an agent describes a worker, a soul describes who that worker is — its persona, principles, and prompt content. Because a soul is just a single-file artifact, you manage it with the same SkillMeat surfaces you already use:
| Capability | How |
|---|---|
Detect & import a SOUL.md |
Standard scan / add flows — soul is an artifact type |
| Register or update a soul from source (offline) | skillmeat add soul SOURCE --update |
| List souls | skillmeat list filtered to the soul type |
| Associate soul ↔ agent | skillmeat artifact link SOUL_ID AGENT_ID --link-type soul_for_agent |
| Inspect associations | skillmeat artifact links SOUL_ID --link-type soul_for_agent |
| Render for a runtime | skillmeat soul render |
Feature flag — default OFF
The soul artifact type is gated by a feature flag and is disabled by default. The flag SKILLMEAT_SOUL_ARTIFACT_TYPE_ENABLED (configured as soul_artifact_type_enabled in skillmeat/api/config.py) gates web UI and API list/filter surfaces only — it does NOT block the local-edition CLI paths (add soul, soul capture, soul associate, soul render), which operate directly on the filesystem and cache DB.
To enable the web UI and API surfaces, set the environment variable to a truthy value before starting the API:
Troubleshooting: If a soul registered via add soul or soul capture is invisible in the web UI but accessible via CLI commands, check this flag first.
What a Soul Looks Like¶
A soul is a single Markdown file (SOUL.md) — typically with YAML frontmatter and a persona body. Souls may contain {{VAR}} placeholders for late binding at render time (for example a {{ AGENT_NAME }} token resolved per deployment target). Because a soul is a tier-0 primitive, it lives in your collection alongside other artifacts and can be deployed, versioned, and composed.
Adding and Listing Souls¶
Souls flow through the same import and detection paths as other artifacts — see Adding Artifacts to Your Collection and Scan & Import Existing Artifacts. Once a soul is in your collection, list it like any other artifact:
# List all artifacts of type soul
skillmeat list --type soul
# Inspect a single soul
skillmeat show soul:my-soul
Souls are referenced by a qualified id of the form soul:<name>. Most soul subcommands also accept a bare name (e.g. my-soul) and qualify it to soul:my-soul for you, or an absolute/relative file path to a SOUL.md on disk.
Registering a Soul via CLI¶
For non-interactive (agent-safe) soul registration, use the add soul subcommand. This is the preferred path when importing a soul from the CLI in a shell context or automation script:
| Flag | Purpose |
|---|---|
--name NAME |
Give the soul a custom name in the collection (optional). When omitted, SkillMeat infers the name from the file. |
--yes |
Skip the interactive trust prompt and proceed non-interactively. The security warning is still printed. Required for CI or non-TTY shells. |
The add soul subcommand works with:
- Local paths: /path/to/SOUL.md or ./my-agent/SOUL.md
- GitHub specs: user/repo/path/to/SOUL.md or user/repo/path/to/SOUL.md@v1.0.0
# Register a local soul
skillmeat add soul ./my-soul.md --name ops-agent --yes
# Import from GitHub
skillmeat add soul anthropics/personas/ops-soul --yes
Registering or Updating a Soul from Source (Offline, Local Edition)¶
skillmeat add soul SOURCE --update is an offline, core-based create-or-update command — it never talks to a running API and works entirely against the local collection filesystem via the core ArtifactManager (add_from_local(on_conflict="update")). This makes it the recommended entry point for unattended agent cycles (e.g. a NUC-hosted agent capturing its own soul) where no authenticated API session is guaranteed to be available.
Local edition only
Offline create-or-update via --update reads and writes the local collection filesystem directly and has no enterprise equivalent. If you are managing souls against an enterprise instance, use skillmeat enterprise add SOURCE --type soul instead (see Enterprise Soul Management below) — do not expect a local --update run to reach or update an enterprise collection.
| Flag | Purpose |
|---|---|
--update |
Create-or-update: if no soul with the resolved name exists, create one; if one exists, overwrite its content in place, preserving uuid and any existing soul_for_agent link. Mutually exclusive with --force (which destructively removes and re-adds, minting a new uuid and orphaning links). Local paths only — GitHub specs do not support --update (use --force there instead). |
--name NAME |
Optional artifact name. Defaults to the source filename stem when omitted. |
--collection NAME |
Target collection (optional; defaults to active collection). |
--yes |
Skip the interactive trust prompt (still prints the security warning). |
# First run registers the soul (create path)
skillmeat add soul ./SOUL.md --update --yes
# Give it an explicit name instead of deriving one from the filename
skillmeat add soul ./SOUL.md --name hermes-soul --update --yes
# Re-run after editing the source — updates in place, preserving identity
skillmeat add soul ./SOUL.md --name hermes-soul --update --yes
# Use in a shell loop to bulk capture/update
for soul in ~/.hermes/*.md; do
skillmeat add soul "$soul" --update --yes
done
Enterprise Soul Management¶
Enterprise deployments manage souls through the API instead of the offline filesystem path above. POST /api/v1/artifacts/upload accepts soul as an artifact type with the same create-or-update semantics: identical content is a no-op, changed content publishes a new immutable version, and first-time content creates the artifact. Drive this from the CLI with:
This requires enterprise.toml with an artifact:write PAT — see the Enterprise Add Guide. There is no offline/filesystem-only path for enterprise souls — every write goes through the authenticated API.
Deprecated: soul capture¶
skillmeat soul capture SOURCE [--name NAME] is a deprecated thin alias for skillmeat add soul SOURCE --update. It calls the exact same core path (add_from_local(on_conflict="update")), but reshapes the result into its own legacy --json schema — the {id, name, action} shape soul capture has always emitted, preserved byte-for-byte across the deprecation boundary so existing scripts that parse soul capture --json keep working unmodified. The only new behavior is a DEPRECATION: warning printed to stderr on every invocation. Prefer add soul --update in new scripts; existing soul capture usage keeps working until the sunset date documented in the deprecation and sunset registry (internal reference).
# Deprecated — still works, emits a stderr warning
skillmeat soul capture ./SOUL.md --name hermes-soul --json
soul capture --json reports which path was taken via the action field (created or updated):
Note: add soul --update --json uses a different JSON shape — the generic command's standard {status, command, artifact} envelope, with no id or action field:
{
"status": "success",
"command": "add",
"artifact": {"name": "hermes-soul", "type": "soul", "collection": "default"}
}
The two commands are not byte-identical to each other — only soul capture's output is byte-identical to its own pre-deprecation shape. If a script depends on the {id, name, action} fields, keep using soul capture (it works until the sunset date above); if the generic envelope is fine, switch to add soul --update.
Associating a Soul with an Agent¶
A soul becomes useful when it is bound to the agent that should wear it. SkillMeat records this with a soul_for_agent link via the generic linked-artifacts API — the same artifact link / artifact links / artifact unlink commands used to link any two artifacts of any type.
Create an Association¶
Both SOUL_ID and AGENT_ID are fully-qualified type:name artifact ids (soul:my-soul, agent:hermes). --link-type is a free string forwarded verbatim to the API — soul_for_agent is just one accepted value alongside requires, enables, and related.
# Associate a soul with an agent
skillmeat artifact link soul:my-soul agent:hermes --link-type soul_for_agent
# Machine-readable output
skillmeat artifact link soul:ops-soul agent:hermes --link-type soul_for_agent --json
On success you'll see a confirmation such as:
List Associations¶
An artifact with zero links returns an empty list, not an error.
# Associations for one soul, as a table
skillmeat artifact links soul:my-soul --link-type soul_for_agent
# Machine-readable output
skillmeat artifact links soul:ops-soul --link-type soul_for_agent --json
Removing an Association¶
Unlinking a link that does not exist fails with a clear 404 error rather than crashing.
Deprecated: soul associate / soul list-associations¶
skillmeat soul associate SOUL AGENT and skillmeat soul list-associations [SOUL] [--json] are deprecated thin aliases that delegate to the generic artifact link / artifact links commands above with --link-type soul_for_agent — same endpoint, same payload shape. Both accept bare names (auto-qualified to soul:… / agent:…) in addition to fully-qualified ids; the agent reference is stored as a slug with no foreign-key constraint, so you can associate a soul with an agent slug that does not yet exist in your collection. soul list-associations with no SOUL argument discovers every soul-type artifact in your collection and aggregates their associations (fanning out to artifact links per soul).
# Deprecated — still works, emits a stderr warning
skillmeat soul associate my-soul hermes
skillmeat soul list-associations my-soul --json
Rendering a Soul¶
skillmeat soul render produces the runtime-ready form of a soul for a specific target platform. It is a thin, on-demand wrapper over the PAL adapter layer — no database write occurs, and rendered output is a build product (never versioned).
SOURCE may be a file path (absolute or relative), a qualified id (soul:my-soul), or a bare name (my-soul) resolved from your collection.
Options¶
| Option | Default | Description |
|---|---|---|
--profile, -p PROFILE |
aos-runtime |
Which target adapter to render through. Renamed from --target-profile to match the generic skillmeat render --profile option name; --target-profile remains a working (hidden) deprecated alias — see note below. |
--set KEY=VAL |
— | Add a literal-string substitution entry to the overlay substitution_table. Repeatable. Replaces each occurrence of the literal string KEY with VAL. This is not the same as {{VAR}} placeholder resolution. |
--out PATH |
stdout | Write the rendered output to PATH. When omitted, output goes to stdout. |
soul render keeps its own pipeline
Only the --target-profile→--profile flag rename is a deprecation; soul render itself is not a deprecated alias for the generic skillmeat render command and is not scheduled for removal. Souls have no ParameterSchema, accept file-path/soul:<name> input, and support stdout output — none of which the generic parameterized-artifact render command supports. The two commands intentionally stay separate.
Target Profiles¶
| Profile | Adapter |
|---|---|
aos-runtime |
Hermes adapter (AOS runtime format) — default |
hermes |
Hermes adapter (alias of aos-runtime) |
codex |
Codex / OpenAI Agents SDK adapter |
generic_markdown |
Pass-through Markdown adapter |
How Rendering Works¶
Rendering is a deterministic two-pass pipeline:
- PAL pass —
{{VAR}}placeholders are resolved non-strictly, so any unresolved variables pass through unchanged rather than erroring. - Adapter pass — the selected target-profile adapter applies declarative overlay transformations (the
substitution_tablebuilt from your--setpairs, plus any platform-specific frontmatter/footer rules) and emits the final content.
A useful guarantee: when the substitution_table is empty (no --set flags), the adapter is a no-op and the output is byte-identical to the PAL-rendered input. The command exits non-zero on failure and writes no partial output.
Examples¶
# Render to stdout using the default aos-runtime profile
skillmeat soul render ./agent-soul.md
# Render a collection soul through the Hermes profile, write to a file
skillmeat soul render hermes-soul --profile hermes --out /tmp/soul.md
# Render with literal substitutions
skillmeat soul render my-soul --set AGENT_NAME=Hermes --set TOOL_DIR=.agents
# Render a qualified collection artifact for Codex
skillmeat soul render soul:ops-agent --profile codex --set FOO=bar
Substitutions vs. placeholders
--set KEY=VAL performs literal string replacement during the adapter pass. To fill {{VAR}} placeholders in the soul body, rely on the PAL pass — these are two independent stages. If you want a {{AGENT_NAME}} placeholder rewritten, the simplest path during a one-off render is to pass the literal token, e.g. --set "{{AGENT_NAME}}=Hermes".
Deploying a Rendered Soul to Multiple Runtimes¶
Because rendering is per-target, a single soul can be deployed to several runtimes (Claude Code, Codex, Hermes / aos-runtime) by rendering once per profile. The declarative rules that drive each target's transformation live in a deployment overlay — see the Deployment Overlays Guide for the full multi-runtime workflow and the OverlayConfig fields.
Worked Example: Hermes Soul Handoff¶
This example shows the full workflow of capturing, associating, and rendering a soul for Hermes deployment.
Scenario¶
You have a runtime soul document at ~/.hermes/SOUL.md (living outside .claude, so sync-pull does not apply). You want to:
1. Register it in your SkillMeat collection as hermes-soul
2. Associate it with the hermes agent
3. Render it for Hermes deployment
Step 1: Register the Soul¶
The soul is now in your collection and discoverable via:
Step 2: Associate with the Hermes Agent¶
Verify the association:
Output:
Links from soul:hermes-soul (1)
┌──────────────┬────────┬────────────┬───────────────────┬─────────────────────────────┐
│ Target │ Type │ Source │ Link Type │ Created │
├──────────────┼────────┼────────────┼───────────────────┼─────────────────────────────┤
│ agent:hermes │ agent │ soul:... │ soul_for_agent │ 2026-07-09T14:22:33+00:00 │
└──────────────┴────────┴────────────┴───────────────────┴─────────────────────────────┘
Step 3: Render for Hermes Deployment¶
Render the soul through the Hermes adapter and write to a deployment path:
Step 4: Update from Source¶
When ~/.hermes/SOUL.md is edited, re-run add soul --update to keep your collection in sync — the identity-preserving upsert overwrites content in place without disturbing the uuid or the soul_for_agent link created in Step 2:
This re-imports the content while preserving the soul_for_agent link to the hermes agent.
See Also¶
- Deployment Overlays — per-target render rules that power multi-runtime deploys
- Deploying Artifacts to a Project — local and Git-PR deployment workflows
- Adding Artifacts to Your Collection — import skills, agents, souls, and more
- Scan & Import Existing Artifacts — auto-discover artifacts in your projects