Skip to content

Extension Loader

Chimera supports two extension surfaces that share a common “directory-of-Markdown” loading idiom:

  1. Custom agentschimera/agents/loader.py (AgentLoader, load_custom_agents, FileAgentDef).
  2. Pluginschimera/plugins/dir_loader.py (DirectoryPluginLoader).

Both walk a conventional directory layout, parse YAML-frontmatter markdown into typed dataclasses, and register the result so the rest of Chimera (REPL /agent, delegate tool, MCP servers, hooks) can discover them. This page covers the directory contracts both loaders honour.

AgentLoader honours three roots, last-loaded-wins:

PriorityRootSource label
1 (lowest)Built-inchimera/builtin_agents/ (packaged)
2User~/.chimera/agents/*.md
3 (highest)Project<project_root>/.chimera/agents/*.md
from chimera.agents.loader import AgentLoader
loader = AgentLoader(project_root="/path/to/project")
loader.load_all()
agents = loader.list_agents() # list[FileAgentDef]
reviewer = loader.get("code-reviewer") # FileAgentDef | None
matches = loader.find_by_trigger("test")

A name registered at a higher priority overrides a lower-priority definition with the same name. AgentLoader._load_from_dir swallows parse errors silently so a malformed in-repo file never breaks startup; tests/agents/ exercises a stricter path that surfaces the errors.

The four packaged subagents (planner, researcher, executor, reviewer) live in chimera/agents/presets/subagents/ — separate from builtin_agents/ because they’re loaded via the older AgentConfig path used by create_default_registry() for backward compatibility:

from chimera.agents.loader import (
builtin_subagents_dir,
create_default_registry,
SUBAGENT_NAMES, # ("planner", "researcher", "executor", "reviewer")
)

See Agent Spawner for the contract.

Every directory loader expects YAML frontmatter followed by a Markdown body that becomes the system_prompt:

---
name: code-reviewer
description: Reviews PRs across four axes
model: claude-sonnet-4-6
tools: [read_file, search, list_files, git, repo_map]
permissions: read_only
loop: react
max_steps: 30
triggers: [review, audit, lint]
skills: [security-review, code-style]
---
You are a careful PR reviewer. Always end with a verdict:
APPROVE / REQUEST_CHANGES / COMMENT.
Frontmatter keyTypeDefaultMeaning
namestrfilename stemUnique agent identifier
descriptionstr""Short blurb shown by /agent list
modelstrNoneModel override
toolslist[str][]Tool names resolved through _TOOL_REGISTRY
permissionsstr"auto_approve"Preset name from _PERMISSION_REGISTRY
loopstr"react"Loop name from _LOOP_REGISTRY
max_iterations / max_stepsint50Max loop turns
triggerslist[str][]Keywords for find_by_trigger matching
skillslist[str][]Skill names injected into system_prompt

FileAgentDef.from_file(path) returns a typed dataclass; the older AgentConfig.from_markdown(path) returns a buildable AgentConfig (documented in Agents & Config). The two formats overlap; new code should prefer FileAgentDef.

DirectoryPluginLoader reads:

my-plugin/
├── plugin.json # manifest (name, version, description, author)
├── agents/
│ └── code-reviewer.md # FileAgentDef
├── hooks/
│ └── hooks.json # list[Hook]
└── .mcp.json # {"servers": {"name": MCPServerConfig}}
from chimera.plugins import DirectoryPluginLoader, ComponentRegistry
plugin = DirectoryPluginLoader().load("/path/to/my-plugin")
plugin.activate(ComponentRegistry())

The activate() call walks each subdirectory and registers what it finds via PluginExtensionRegistry:

FileRegistered via
agents/*.mdPluginExtensionRegistry.register_agent
.mcp.jsonPluginExtensionRegistry.register_mcp_server
hooks/hooks.jsonPluginExtensionRegistry.register_hook

chimera.skills.discovery.discover_skills(roots) walks SKILL.md files with YAML frontmatter and returns ready-to-inject SkillDefinition instances. Skills are not loaded by the directory plugin loader directly; they’re discovered at REPL startup or explicitly registered via the agent registry’s skills: frontmatter key.

See Skill Discovery for the discovery roots and SKILL.md format.

For one-off use without an AgentLoader:

from chimera.agents.loader import (
create_default_registry,
load_custom_agents,
)
registry = create_default_registry()
loaded_names = load_custom_agents(registry, "./.chimera/agents")
print(loaded_names) # e.g. ["my-reviewer", "my-tester"]

create_default_registry() pre-loads the five long-lived presets + the four subagent profiles; load_custom_agents adds your project’s overrides on top.