Nine AI Employees, One YAML File Each

2026-10-15agentsdeclarativeone-person-companyhermes

Nine AI Employees, One YAML File Each

_An opinionated tour of the .agent.md system I run my one-person company on, and why "declarative" is doing more work than "AI" in that sentence._

The shape of the thing

Most "AI agent" codebases I've seen look like this:

agents/
  customer_service.py       # 400 lines
  finance.py                # 700 lines
  monitoring.py             # 500 lines
  ...

Each file defines its own tool wiring, prompt, retry policy, memory backend, and I/O schema. Change one thing — say, add a tool or swap a provider — and you touch every file.

Mine looks like this:

agents/definitions/
  customer-service.agent.md   # 30 lines
  finance.agent.md            # 28 lines
  monitoring.agent.md         # 32 lines
  freelancer-scanner.agent.md # 26 lines
  ...

Every definition is one YAML-frontmatter block plus a prose role description. There's no Python per agent. The Python is in the framework (Hermes Agent), which loads them at boot.

A real one, in full

---
type: agent
name: fitness-coach
description: "体测、训练计划、饮食、进度追踪"
version: "1.0.0"
role: assistant
priority: P1
domain: health
capabilities:
  - parse-body-composition-report
  - generate-weekly-training-plan
  - track-diet-compliance
  - summarize-progress-vs-goal
tools:
  - name: health-log-reader
    type: storage
  - name: plan-generator
    type: llm
  - name: progress-notifier
    type: api
protocols:
  - acp
  - gateway
dependencies: []
status: active
---

Coach the user through weekly training and diet cycles. Read the previous
week's body-composition report, propose next week's plan, log it, and push
a summary through Telegram on Sunday evening. Never invent a body weight
that isn't in the log.

That's it. No class FitnessCoachAgent(BaseAgent). No async def process(). The framework discovers type: agent, reads capabilities, wires tools from the tool registry, exposes the whole thing over ACP and the gateway, and the prose becomes the system prompt.

What "declarative" is doing

Three things, in the order I actually needed them:

1. Diff-ability. The last time I changed customer-service behavior, the git diff was 4 lines of YAML. Reviewing that diff took 20 seconds. Reviewing a 40-line Python diff takes much longer. When you're the only reviewer, that difference compounds.

2. Portability. Because the agent definition and the framework are decoupled, I moved from a legacy BaseAgent pattern to Hermes without touching any agent's role or tools. The prose is identical; only the loader changed.

3. Composability. The orchestrator (also declarative — a SQLite loop.db plus a qoder_orchestrator.py helper) reads every .agent.md's domain and priority and dispatches tasks accordingly. No Python if isinstance(agent, CustomerServiceAgent). That dispatch is data.

The tool registry is the other half

Every agent's tools list is just names. Those names resolve against a central registry:

# tools/customer_service.py
from tools.registry import registry

def search_knowledge_base(query: str, top_k: int = 5, task_id: str = None):
    ...

registry.register(
    name="knowledge_base",
    toolset="customer_service",
    schema={...},
    handler=lambda args, **kw: search_knowledge_base(...),
    check_fn=lambda: bool(os.getenv("KB_URL")),
    requires_env=["KB_URL"],
)

Any file under tools/ with a registry.register() call is discovered on import. That means an agent's toolset can be defined in a completely different repo, and .agent.md just references it by name. The contract is the schema, not the class.

The nine

AgentPriorityDomainStatus
CustomerServiceAgentP0crm90% — in production for one business line
FinanceAgentP0finance60% — commission calc working, payouts manual
MonitoringAgentP0operations80% — alerts routed via Telegram
FreelancerScannerP1freelancing30% — scanner runs, matching rules need data
FitnessCoachAgentP1health70% — plans + logging stable, diet tracking rough
ContentCreatorAgentP1content30% — drafts work, publishing pipeline not wired
EduTutorAgentP1education30% — concept explanations good, exercises weak
CodeForgeAgentP2engineering30% — used informally as an internal tool
DesignBotAgentP2design30% — brand consistency hard, parked

Two agents earn revenue today (CustomerService, FitnessCoach, in one business line each). The rest are either internal automation or on deck for the next contract. Nine feels about right; three feels underbuilt, twenty feels like you can't maintain any of them.

What I got wrong first

The initial version had each agent's role as freeform text. That was fine until I tried to auto-route tasks by role, and every routing table had to hand-enumerate role strings. Adding role: scanner | analyst | assistant | monitor as an enum turned a 40-line dispatch function into a 4-line dict lookup. Enums beat strings, always.

Second mistake: dependencies started as freeform and became a list of agent names. The framework uses it to order boot-up so a monitor can't start before the metrics source it depends on. It's not a hard constraint yet, but the field exists and I've started taking it seriously.

Third mistake: I had a model: field per agent (which LLM to use). That turned into operational hell — every agent had an opinion about a provider's price changes. Removed it; the framework picks model based on task class and there's a single global provider config.

Try it on your own

The .agent.md frontmatter is a small spec — I wrote a generator for it so you don't have to memorize the fields. Fill out a form, download the file: openddd.com/tools/agent-md-generator.

It emits valid YAML even if you're not going to run Hermes. If you are, drop it in your agents/definitions/ and re-boot. If you're not, use the schema as a checklist — those nine fields cover most of what I actually think about when I add a new agent.