Subagents - Z-M-Huang/openhive GitHub Wiki
Subagents are the execution layer within a team. The orchestrator ALWAYS delegates tasks to subagents — it never invokes skills directly (ADR-40). Each subagent brings a different perspective and follows its own set of skills.
This page covers subagent definition files, the subagent-only invariant, and wiring. For the overall rule system, see Rules-Architecture. For reusable procedures that subagents follow, see Skills. For learning and reflection cycles, see Self-Evolution.
graph TD
orch["Team Orchestrator<br/>(manages subagents, never invokes skills directly)"]
subgraph agents["Subagents (perspectives)"]
a1["loggly-monitor<br/>Skills: get-loggly-log, alert-check"]
a2["incident-responder<br/>Skills: incident-response"]
end
subgraph skills["Skills (procedures)"]
s1["get-loggly-log.md"]
s2["alert-check.md"]
s3["incident-response.md"]
end
subgraph plugins["Plugins (executable tools)"]
p1["loggly_fetch.ts"]
p2["classify_entries.ts"]
p3["pagerduty_notify.ts"]
end
orch -->|"invokes"| a1
orch -->|"invokes"| a2
a1 -->|"follows"| s1
a1 -->|"follows"| s2
a2 -->|"follows"| s3
s1 -->|"Required Tools"| p1
s2 -->|"Required Tools"| p1
s2 -->|"Required Tools"| p2
s3 -->|"Required Tools"| p3
Orchestrators ALWAYS delegate to subagents. No direct skill invocation by orchestrators. A {subagent}.md is always loaded into context for task execution.
This means:
- Every task that enters a team is routed by the orchestrator to a specific subagent
- The orchestrator reads subagent definitions to decide which subagent handles a given task
- If no existing subagent fits, the orchestrator can create a new one or escalate
The main agent is NOT a team orchestrator. It routes user requests to child teams and delegates. It has:
- No subagents
- No skills
- No learning or reflection triggers
- No direct task execution
The main agent's only tools are: channel adapters, organization tools (spawn_team, delegate_task, escalate, etc.), and list_teams for routing decisions.
| Responsibility | Description |
|---|---|
| Task execution | Follow skills, use plugins to perform work |
| Learning cycles | Own nightly learning triggers (see Self-Evolution#Autonomous Learning) |
| Reflection cycles | Own nightly reflection triggers (see Self-Evolution#Self-Reflection) |
| Self-evolution proposals | Identify issues in own files, propose changes, escalate to orchestrator for confirmation |
| Notification decisions | Decide whether results warrant user notification (actionable findings only) |
| Layer | Responsible for | If stuck |
|---|---|---|
| Main agent | Identifying which team owns the problem | Asks user for clarification |
| Orchestrator | Choosing the right subagent, confirming proposed changes | Escalates to main if no subagent fits |
| Subagent | Diagnosing issue, proposing fix, executing within boundaries | Escalates to orchestrator if out of scope |
| Skill | Step-by-step procedure | Subagent adapts or proposes skill revision |
| Plugin | Single API call execution | Skill handles errors per its steps |
For a complete walkthrough of how user requests flow through the full hierarchy (Main → Orchestrator → Subagent → Skill → Plugin), see Scenarios#User → Fix Flow.
Subagent definitions live in .run/teams/{name}/subagents/, one markdown file per agent.
Example ops-team directory:
.run/teams/ops-team/
├── config.yaml
├── org-rules/
├── team-rules/
├── subagents/
│ ├── loggly-monitor.md
│ └── incident-responder.md
├── skills/
│ ├── get-loggly-log.md
│ ├── alert-check.md
│ └── incident-response.md
└── plugins/
├── loggly_fetch.ts
├── classify_entries.ts
└── pagerduty_notify.ts
Each subagent definition file contains four sections:
| Section | Purpose |
|---|---|
| Role | What the agent specializes in |
| Skills | Which skills this agent follows, each with its purpose |
| Boundaries | Scope limits and escalation conditions |
| Communication Style (optional) | How the agent reports progress and findings |
The file heading names the agent (e.g., # DevOps Agent).
Subagent definitions are loaded by skill-loader.ts from .run/teams/{name}/subagents/ and converted into AI SDK tool() definitions by subagent-factory.ts. Each subagent tool wraps a generateText() call, giving the orchestrator a way to delegate focused subtasks with isolated context. The orchestrator sees only the final result text, not intermediate tool calls.
Subagents only notify the user on actionable findings (errors, warnings, fatals). Clean checks and routine results are stored in the task queue but not pushed to any channel. This prevents noise from routine operations.
For early-exit monitoring behavior when a clean check completes, see Skills#Early-Exit Monitoring.
Subagents can identify issues in their own files (subagent.md, skills/*.md, plugins/*.ts) and draft proposals for changes. However, subagents do NOT apply changes directly. They escalate to the orchestrator for confirmation via the propose+confirm model.
See Self-Evolution#Evolution Flow for the full propose+confirm sequence.
When a subagent is targeted by a window trigger (ADR-42; see Triggers#window Trigger Type), its Responsibilities section MUST declare three items explicitly — these are enforced invariants, not conventions:
| Required Declaration | What It States |
|---|---|
| Cursor keys | Exactly which memory keys the subagent reads at tick start and writes at tick end. Keys MUST be namespaced as <subagent_name>:<cursor_name> (e.g., news-scanner:last_event_id) to prevent collision when multiple subagents share a team. |
| No-op return contract | Commitment to return { action: "noop", reason: string } when the tick's scan finds nothing actionable. The trigger engine treats any other shape as a real result. See Tool-Guidelines#No-op Tick Contract. |
| Idempotency invariant | One-sentence statement of why repeat ticks over the same data produce no duplicate work (typically "cursor advances monotonically and external side effects are gated by cursor"). |
Copy this block into the subagent's Responsibilities section and fill in the blanks:
## Responsibilities
### Cursor keys
- Reads: `<subagent_name>:last_scan_cursor` at tick start.
- Writes: `<subagent_name>:last_scan_cursor`, `<subagent_name>:last_event_id` at tick end.
- Namespace: all cursor keys prefixed with `<subagent_name>:` per [[Tool-Guidelines#Cursor Discipline]].
### No-op return contract
If the scan finds no new items beyond `last_scan_cursor`, this subagent returns:
`{ "action": "noop", "reason": "no new items since <cursor value>" }`
Any other structure is treated by the engine as a real result. See [[Tool-Guidelines#No-op Tick Contract]].
### Idempotency invariant
Cursor `<subagent_name>:last_scan_cursor` advances only when new items have been durably processed; external side effects (e.g., `enqueue_parent_task`) are gated by cursor advancement, so replay of a tick produces no duplicate work.Teams whose subagents are NOT targeted by a window trigger do not need these declarations.