Elnor Repo Reader

ELNOR_SUBAGENT_ARCHITECTURE_V5_2_AUDITED.md

Current Specs/Miscellaneous Specs/ELNOR_SUBAGENT_ARCHITECTURE_V5_2_AUDITED.md

Generated 2026-06-09T01:23:58.539Z from commit dbaa25962edc11ab30e8d4ca1715f9ae5bf77331. Worktree: clean.

Open text page · Open raw txt · Open path URL

# ELNOR Sub-Agent Architecture and Capability Registry — Operative Spec V5.2

**Date:** 2026-05-18  
**Status:** Operative spec draft produced from V5.1 Adjudication Card V4. V5.2 supersedes `ELNOR_SUBAGENT_ARCHITECTURE_V5_1.md` in full and supersedes V4 §1 in full. V4 §§2–4 — Mid-Turn Knowledge Retrieval, Pre-Computation, and Tool Optimizations — remain operative as written unless separately amended.  
**Supersedes:** `ELNOR_SUBAGENT_ARCHITECTURE_V5_1.md` in full.  
**Adjudication source:** `Subagent_V5_1_Adjudication_Card_V4.md` (2026-05-18).  
**Companion specs referenced:** DOC24 R3.1.1; DOC72 R5.73; EC Core Addendum A V3.3 plus EC Core primary agent-configuration registry; MultiDoc PropA R6.3; DOC23 Addenda B Core R0.7; DOC23 Evaluation Common Contracts V1.1; DOC23 Addenda B Outcome Evaluator/Revisor V3.3; DOC23 Addenda B Feedback Delivery V1; DOC11/OpenClaw runtime; DOC12 rooms; DOC20 Q UI; DOC25 document intelligence; DOC73 V1.5.1 (V1.6 queued for next minor where referenced); BDSM/DOC8 utility ledgers; OP-A V3.15+.

---

## V5.2 change summary from V5.1

V5.2 folds the accepted V5.1 red-team adjudications into the operative sub-agent architecture. The revision is substantive, not cosmetic. The main changes are:

1. **Standing-order prompt rewritten and budgeted.** §1.4 replaces the over-delegation-oriented V5.1 prompt with a tighter orchestration prompt that makes user-directed dispatch highest priority, prevents hypothetical native sub-agent use, removes model-quality bias, adds verifier/hierarchical/dry-run guidance, and keeps final composition non-delegable.
2. **Dispatch admission is now enforceable.** §2.8 defines a canonical 8-step dispatch admission order, a complete `SubAgentDispatchEnvelopeSchema`, a receipt lifecycle schema, an EC/PropA exposure gate, effective tool/auth grant computation, output-taint handling, contribution tracing, and cancellation/partial-result behavior.
3. **Policy and exposure are first-class.** Every sub-agent dispatch is a context exposure event evaluated by EC Core's compiled `PolicyDecisionEngine`. Capability `allowed_input_classes` / `forbidden_input_classes` are discovery metadata, not enforcement truth.
4. **Privilege expansion blocked.** Sub-agents do not inherit parent auth merely because OpenClaw can do so. Effective grants are intersections across runtime eligibility, EC identity grants, capability limits, call-side allowlists, policy, budget, and matter/workspace access.
5. **DOC24 slot conformance fixed.** `available_subagents` is registered as `DOC24.available_subagents` using DOC24 R3.1.1 `InjectionSlotRegistry` shape.
6. **Capability schema extended.** DOC72 `subagent_capability` profiles now include `capability_type`, `semantic_actions`, optional `semantic_action_specs`, `availability_source`, `health_state`, `display_name_cache`, aliases, `allowed_coordination_points`, DOC72/DOC24-aligned scope vocabulary, and staged lifecycle.
7. **Task Agent added as a system specialist.** Task Agent is pre-registered with 15 typed entrypoints, but is excluded from ordinary `available_subagents` rendering unless DOC24/DOC23 task-mode routing surfaces it.
8. **Pattern taxonomy expanded and bounded.** V5.2 adds Pattern G verifier/critic loops and Pattern H orchestration-plan dry-runs; strengthens Pattern E with spawn-tree controls and coordination protocol; strengthens Pattern F with room lifecycle, decline memory, closure behavior, @mention routing, and DOC23 Task Forum / Run Board boundary.
9. **DOC23 seam corrected.** V5.2 explicitly carves out DOC23 Addenda A §A7 task-module specialist dispatch from Elnor-level `dispatch_to_subagent`; DOC23 task-module traces attach to task/run/module truth.
10. **Utility ledger split.** BDSM sub-agent utility separates advice quality from infrastructure reliability, tracks capability versions, supports deferred advice resolution, and treats weights as initial calibrations.
11. **Caching and conditional rendering added.** Static orchestration-layer caching is specified via adapter metadata. Empty `available_subagents` slots render nothing.
12. **OP-A coverage expanded.** V5.2 adds or updates obligations for DOC10 room routing, DOC25 document intelligence tools, EC agent commands, DOC72 capability hydration, EC background work registry, DOC24 Task Agent registration, BDSM learning visibility scope, unified specialist ledger, and DOC23 slot-registration mechanics.

---

## §0 Posture

ELNOR is a single-user, local-first AI orchestration platform running on OpenClaw native. OpenClaw's `sessions_spawn` provides the runtime substrate for sub-agent execution. ELNOR adds policy, context, registry, state, learning, and user-facing orchestration around that substrate.

V5.2 is **delegation-capable**, not delegation-maximal. Elnor should use sub-agents when they improve wall-clock speed, context hygiene, bounded specialization, verification quality, cost discipline, or user observability. Elnor should not spawn for ordinary conversation, trivial tool calls, single local knowledge-graph queries, or work that would be cheaper and clearer inline.

The governing invariant remains:

```text
Sub-agents do work. Elnor owns the conversation, the final user-facing composition, and judgment about what the user needs.
```

Named specialists are persistent deployment configurations. They are not proof that a stronger model is needed. Dispatch to a specialist because its identity, instructions, tools, budget, scope, or typed action fits the task.

---

## §1 Sub-Agent Orchestration

### §1.1 What OpenClaw provides natively

OpenClaw provides native sub-agent execution through `sessions_spawn`:

- **Non-blocking spawn.** `sessions_spawn` returns immediately with `{ status: "accepted", runId, childSessionKey }`.
- **Push-based completion.** Finished child sessions announce status and result metadata back to the parent.
- **Session isolation.** Each sub-agent gets its own context and token usage.
- **Auth inheritance fallback.** OpenClaw can inherit parent auth profiles, but V5.2 blocks implicit privilege expansion through §2.8C effective grant computation.
- **Configurable nesting.** `maxSpawnDepth` default 1, range 1–5; ELNOR baseline 3.
- **Concurrency controls.** `maxChildrenPerAgent` default 5, range 1–20; `maxConcurrent` default 8 globally; `runTimeoutSeconds` recommended 900.
- **Session archival.** `archiveAfterMinutes` default 60; this is post-completion retention, not execution timeout.
- **Model override per spawn.** Default inherits from spawning agent.
- **Tool restrictions.** OpenClaw default tool inheritance is not sufficient; V5.2 applies effective grant intersection before dispatch.
- **Named-agent spawning.** `sessions_spawn(agentId: "X")` invokes a registered agent. Cross-agent workspace resolution requires OpenClaw ≥ v2026.3.13.
- **Optional forked context.** `context: "fork"` copies parent transcript up to `session.parentForkMaxTokens` default 100,000; beyond that, fork falls back to isolated. V5.2 requires fallback visibility in receipts.
- **Cascade cleanup.** Stopping the parent stops active sub-agents.

Open OpenClaw issue: **#50263 — named-agent SOUL.md not auto-injected when spawning by agentId.** ELNOR's dispatch path explicitly loads SOUL.md content from the EC Core agent identity record and prepends it to the spawn task prompt until the issue is resolved.

### §1.2 What isolated sub-agents do not receive natively

A sub-agent spawned with `context: "isolated"` does not automatically receive ELNOR's DOC72 knowledge graph content, DOC1/DOC72 memory directives, active matter context, or parent transcript.

DOC24 supports `sub_agent_context_pack` assembly mode for bounded task context. When Elnor dispatches a sub-agent for matter-scoped work, the spawn request may include relevant entity card refs, active constraints, procedure or goal context, matter id, policy snapshots, and explicitly attached input artifacts.

Forked context and `sub_agent_context_pack` are complementary:

- `fork` copies recent raw transcript.
- `sub_agent_context_pack` supplies structured, policy-screened context.

Fork is appropriate only when the worker needs recent conversational continuity and the transcript is below the fork limit. Otherwise, send a structured context pack.

### §1.3 When to use sub-agents

#### §1.3.1 Non-delegable work

- The user's conversation with Elnor.
- The final composition the user sees.
- Judgment about what the user needs.
- Elnor's voice and persona.

Sub-agents may produce findings, drafts, alternatives, or verifier findings. Elnor decides what to use and writes the final response.

#### §1.3.2 Delegable work

- Research and retrieval.
- Document analysis and OCR/chunk processing.
- Parallel investigations.
- Drafting components that Elnor revises.
- Registered specialist domain work.
- Substantial reasoning within bounded scope.
- Repetitive structured work.
- Background monitoring under EC background work registry control.
- ELNOR Core memory searches via MemoryAgent when multi-hop or retrieval judgment is needed.
- Document intelligence via DocumentIntelligenceAgent.
- Task Agent task-design and task-inspection work when DOC23/DOC24 task-mode routing authorizes it.
- DOC23 task-module work only through DOC23-owned §A7 dispatch paths, not V5.2 `dispatch_to_subagent`.

#### §1.3.3 Decision heuristics

Spawning is likely appropriate when two or more apply:

1. **Speed / wall-clock time.** Independent subtasks can run in parallel.
2. **Context bloat avoidance.** Inline work would create large intermediate material the user does not need to see.
3. **Specialization benefit.** A registered specialist's identity, tools, instructions, scope, or typed action fits materially better.
4. **Failure boundary.** The work is exploratory, retry-tolerant, or risky enough that isolation helps.
5. **Restart tolerance.** The subtask can be retried cleanly.
6. **Spawn-cost threshold.** Spawning has overhead around 2–3K tokens. If inline work is under that range, spawning usually costs more than it saves.
7. **Subject hygiene.** The work can be described with a specific task and success criteria.
8. **Verification value.** A maker-checker pass is cheaper than user review or materially reduces risk.

Internal local knowledge graph queries that take milliseconds are not spawn candidates.

#### §1.3.4 When not to spawn

- Conversational responses.
- Simple single-step tool calls.
- Internal graph lookups.
- Work under roughly 2–3K tokens of inline work.
- Tasks requiring live user interaction.
- Tasks needing final Elnor composition only.
- Work where the subtask cannot be bounded clearly.

### §1.4 Canonical standing-order prompt text (NORMATIVE)

**This text is the operational instruction injected into Elnor's SOUL.md or sibling standing-orders layer for every session. It is normative. The rest of V5.2 is design and implementation documentation unless explicitly stated as injected text.**

```text
SUB-AGENT ORCHESTRATION

You are ELNOR Core's orchestrator. OpenClaw executes spawns. Your
native reasoning works as usual; the tools below are ELNOR's only
spawn mechanisms — do not invoke hypothetical native sub-agent
primitives outside them.

USER-DIRECTED DISPATCH (highest priority):
  When the user names a specialist ("use Janet", "use my
  LegalResearchAgent"), dispatch to that specialist. If the name
  matches multiple, ask which one.

THREE TOOLS:
  sessions_spawn — ad-hoc worker on your model (or model_override).
    Default context: isolated. Use "fork" only when the worker needs
    your recent transcript AND the transcript is <100K tokens.

  dispatch_to_subagent — registered specialist with own persona,
    tools, budget. Specialists are configurations, not capability
    upgrades — don't dispatch for a "stronger model." Relevant ones
    appear in available_subagents when populated.

  create_agent_room + post_to_room — DOC12 room for multi-agent work
    the user wants to watch. Propose first ("Want me to open a
    room?"); create only on user accept.

DELEGATE WHEN AT LEAST ONE APPLIES:
  - Multiple subtasks can run in parallel.
  - Intermediate work (tools, doc scanning) would clutter your context.
  - A registered specialist's specialty fits better than inline.
  - Natural sub-decomposition; a "lead" can spawn specialists
    (hierarchical, depth ≤ 3).
  - You want a critic to verify your output before composition (spawn
    a verifier).

DO NOT DELEGATE:
  Trivial single-step lookups, internal knowledge-graph queries (<50ms),
  conversational responses, anything under ~2-3K tokens of inline work,
  or work requiring live user interaction.

NON-DELEGABLE:
  Your conversation with the user.
  The final composition the user sees (you compose in your voice;
  sub-agents draft components you revise).
  Your judgment about what the user needs.

SPAWN HYGIENE:
  Every spawn needs a specific task and success criteria. Include
  matter context and active constraints. Vague spawns wander.

DRY-RUN: For multi-spawn work (≥5 spawns OR ≥$5 estimated), consider a
  DOC24 dry-run preview unless the user directed immediate execution.

FAILURE: If a spawn fails or returns low-utility output, retry once only
  if policy, budget, and side-effects permit. Prefer same-class fallback;
  never retry side-effecting work without authorization.
```

### §1.4A Prompt injection timing, budget, and DOC23 coexistence

**Where the prompt lives:** EC Core operative SOUL.md or sibling standing-orders file. Injected through the EC/Core prompt assembly path.

**When injected:** every new Elnor session at startup. Persistent for the session. The `available_subagents` slot is per-activation and dynamic; this standing order is static.

**Budget.** The §1.4 standing order is budgeted to remain at or below 450 tokens under every deployed tokenizer family. EC Core build-time CI MUST tokenize the exact injected text for cl100k_base, o200k_base, Claude, Gemini, Kimi, and other deployed tokenizers where available. The build fails if any deployed tokenizer counts §1.4 above 450 tokens or the combined static orchestration layer (§1.4 plus DOC23 §3B.4 ambient task-awareness card) above 650 tokens.

**Zero-specialist installations.** The standing order is invariant. It ships even if no user-created specialists are registered. In a fresh installation, only pre-registered system specialists are available. The `available_subagents` slot renders when relevant and renders nothing when no specialists match.

**Coexistence with DOC23 Addenda B task-system routing.** V5.2's standing order is the default for ordinary chat and L0–L2 work under DOC23 §4C.8. DOC23 owns the explicit L3–L5 task-system carve-out. Turn-start routing composes as follows:

**DOC23 task-signal vocabulary used at the seam.** DOC24 TaskModeDecision and Pattern H mid-stream escalation evaluate DOC23 §3C.6 positive task signals against §3C.7 veto signals. V5.2 references these signals instead of creating new task-escalation vocabulary. Positive task signals are:

```text
explicit_task_request
existing_task_name_referenced
existing_template_name_referenced
matching_template_high_confidence
long_running
recurring_or_monitoring
multi_step_workflow
multiple_independent_artifacts
requires_human_gates
requires_audit_trail
requires_scheduled_or_triggered_execution
requires_background_progress
high_stakes_repeatable_process
user_asked_to_set_up_process
known_user_preference_for_task_in_context
existing_invocation_directive_match
task_output_or_run_question
```

Veto signals are:

```text
simple_single_turn_answer
simple_tool_call
ordinary_conversation
user_requested_quick_response
primary_conversation_should_remain_direct
insufficient_task_specificity
no_formalization_value
task_system_overhead_exceeds_value
user_recently_rejected_task_suggestion
user_prefers_chat_native_for_context
requires_live_iterative_conversation
one_off_low_stakes_result
```

Complexity alone is not the routing signal; formalization value and substrate need are. A 10-spawn parallel research workflow can remain L2 if outputs return to chat. A 3-step drafting workflow can be L3 when the output is the deliverable and needs gates, audit, checkpoints, artifacts, or reuse.

1. DOC24 TaskModeDecision runs first, scoring DOC23 §3C.6 positive task signals against §3C.7 veto signals.
2. If a task-system mode is selected, task-system primary actions take precedence for that turn's primary work: existing saved task, Task Agent design/review, run inspection, output retrieval, recurring saved task, or EC scheduled job. Elnor still handles surrounding conversational explanation.
3. If a non-task mode is selected, V5.2 applies. Elnor may spawn, dispatch, verify, open rooms, or run orchestration-plan dry-runs.
4. During Pattern H planning, Elnor may offer a task transition if planned work newly triggers ≥3 DOC23 §3C.6 positive task signals and no strong §3C.7 veto applies. User acceptance routes to Task Agent; user decline continues in V5.2 and records workstream decline memory.

V5.2's §1.4 prompt MUST NOT include DOC23 escalation language. Task-system surfacing is owned by DOC24 TaskOpportunityPacket and the DOC23 ambient task-awareness pathway, not by freehand prompt instructions.

### §1.4B Caching discipline for the static orchestration layer

The static orchestration layer comprises:

- V5.2 §1.4 standing order.
- DOC23 Addenda B Core R0.7 §3B.4 ambient task-awareness card when configured.
- Static tool definitions for `sessions_spawn`, `dispatch_to_subagent`, `create_agent_room`, and `post_to_room`.

This layer is invariant across turns and MUST be marked cacheable by EC Core's runtime adapter when the active provider supports caching. Variable content — memory injection, `available_subagents`, matter context, conversation history, TaskOpportunityPacket, and dynamic slots — MUST live outside the cached region.

```ts
export const ProviderCachingCapabilitySchema = z.object({
  provider_id: z.string(),
  model_family: z.string(),
  caching_supported: z.boolean(),
  caching_mode: z.enum([
    "automatic_prefix",
    "explicit_cache_control",
    "context_cache_object",
    "adapter_local",
    "none",
  ]),
  minimum_prefix_tokens: z.number().int().nonnegative().optional(),
  configured_cache_cost_multiplier: z.number().nonnegative().optional(),
  source_ref: z.string(),
  last_verified_at: z.string().datetime(),
  schema_version: z.literal(1),
});
```

Provider caching mechanisms and prices are adapter metadata, not frozen V5.2 normative values. EC Core diagnostics read the adapter record to show current caching state.

### §1.4C DOC24 implementation dependency for natural task surfacing

V5.2's natural surfacing of DOC23 task options depends on DOC24 implementing the DOC23 Addenda B Core R0.7 §24A.1 obligations tracked in OP-A V3.15. The key dependency mapping is:

| V5.2 feature | OP-A V3.15 row(s) |
|---|---|
| Automatic chip on task signals | `OBL-D24-TASK-MODE-RESOLVER-01` + `OBL-D24-TASK-OPPORTUNITY-PACKET-LANE-01` |
| Top-k template suggestion in chip | `OBL-D24-TOPK-INJECTION-01` |
| No full-TKP stuffing protection | `OBL-D24-NO-FULL-TKP-IN-CHAT-01` |
| Accept/reject feedback | `OBL-D24-TASK-INVOCATION-DIRECTIVE-ROUTING-01` + `OBL-D8-TASK-SUGGESTION-FEEDBACK-01` |
| Task Agent dispatch after chip acceptance | `OBL-D24-TASK-AGENT-CAPABILITY-01` + `OBL-D24-TASK-AGENT-ENTRYPOINTS-01` |

Until DOC24 R3.2+ implements those rows, V5.2 operates in graceful degradation mode:

- Explicit task references route through existing task capability dispatch.
- Elnor can still reason from the ambient task-awareness card when present.
- Automatic chip firing does not operate until DOC24 emits TaskOpportunityPacket.
- The §1.4 prompt does not change when DOC24 implementation lands; upstream signal availability changes.

### §1.5 Configuration

Sub-agent model defaults to the spawning agent's model unless the dispatch envelope specifies `model_override` and policy/budget allow it.

```json
{
  "agents": {
    "defaults": {
      "subagents": {
        "maxSpawnDepth": 3,
        "maxChildrenPerAgent": 5,
        "maxConcurrent": 8,
        "runTimeoutSeconds": 900,
        "archiveAfterMinutes": 60,
        "maxSpawnsPerMinute": 30,
        "maxSpawnsPerHour": 200
      }
    }
  }
}
```

```json
"runTimeoutSeconds": 900,
"archiveAfterMinutes": 60
```

`runTimeoutSeconds` is an execution deadline. `archiveAfterMinutes` is post-completion retention. They are not interchangeable.

`maxSpawnDepth` is OpenClaw global. DOC23 Addenda A §A7.5.2 `max_call_depth` is task-module per-spawn. Both apply simultaneously; the tighter cap fires first.

### §1.6 Cross-doc implications

- **DOC24:** capability registry, packet assembly, `sub_agent_context_pack`, `DOC24.available_subagents`, dispatch admission, dispatch receipt, exposure gate consumption, effective grant, injection manifests.
- **DOC72:** `subagent_capability` node kind and capability-profile payload. DOC72 stores discovery metadata and capability truth, not EC-owned execution identity.
- **EC Core:** standing-order injection, system-agent package records, effective runtime state, compiled policy engine, background work registry, command closure, cost governance.
- **PropA:** sensitivity, data-class, taint, sharing, and exposure semantics consumed through EC PolicyDecisionEngine.
- **DOC11/OpenClaw:** final runtime execution truth and native `sessions_spawn`.
- **DOC10:** room-message routing, @mention routing, active child run state.
- **DOC12:** rooms, room blackboard, room lifecycle, room transcript retention.
- **DOC20:** Agents page, room surfaces, Q dashboard spawn-tree and slot-pressure inspector.
- **DOC23:** Task Agent, saved-task modes, TaskModuleSubAgentTrace, Addenda A §A7 task-module boundary, Addenda B task-routing seam.
- **DOC25:** DocumentIntelligenceAgent logical tool mappings.
- **BDSM/DOC8:** sub-agent utility ledgers, learning suppression, deferred advice resolution, unified future specialist ledger.

### §1.7 Pattern taxonomy

Patterns are not mutually exclusive. Dispatch admission, policy gating, effective grants, receipts, and output taint apply to all patterns.

#### §1.7.A Pattern A: Specialist retrieval and processing

Use a registered specialist when its persistent identity, tools, instructions, scope, typed action, or reputation makes it materially better suited than inline work or an ad-hoc worker.

#### §1.7.B Pattern B: Decomposition reasoning

Split independent subtasks across ad-hoc workers or specialists; synthesize completed lanes. Missing lanes must be marked explicitly when incompleteness affects confidence.

#### §1.7.C Pattern C: Background and monitoring

Background sub-agents run outside the user's immediate wait path but remain user-visible in EC background work status. Pattern C is not a DOC23 saved task unless DOC23 formalization is selected.

**EC background work registry integration is required.**

```ts
export const SubAgentBackgroundWorkRegistrationSchema = z.object({
  registration_id: z.string(),
  invocation_id: z.string(),
  parent_session_id: z.string(),
  cadence_ref: z.string().optional(),
  budget_ref: z.string().optional(),
  cancellation_command_ref: z.string().optional(),
  ec_background_orchestrator_handle: z.string(),
  visible_in_q_dashboard: z.boolean().default(true),
  created_at: z.string().datetime(),
  updated_at: z.string().datetime(),
  schema_version: z.literal(1),
});
```

If EC processing is paused, incognito prohibits automatic expansion, or budget is exhausted, Pattern C emits a blocked/degraded receipt instead of launching hidden work.

#### §1.7.D Pattern D: Named user-registered domain specialists

When the user names a specialist, resolution follows §2.4. User-directed dispatch is the highest-priority dispatch signal after safety/policy gates.

#### §1.7.E Pattern E: Hierarchical cascading decomposition

A lead specialist may coordinate downstream specialists when work naturally decomposes. Pattern E requires spawn-tree control and explicit coordination protocol for depth ≥ 2.

```ts
export const SubAgentSpawnTreeControlSchema = z.object({
  spawn_tree_id: z.string(),
  parent_invocation_id: z.string().optional(),
  depth: z.number().int().min(0).max(5),
  may_spawn_children: z.boolean().default(false),
  max_children_for_this_node: z.number().int().min(0).max(20).default(0),
  max_total_descendants_remaining: z.number().int().min(0).max(100).default(0),
  budget_remaining: z.object({
    max_cost_usd: z.number().nonnegative().optional(),
    max_tokens: z.number().int().nonnegative().optional(),
    max_duration_seconds: z.number().int().nonnegative().optional(),
  }).default({}),
  anti_loop_keys: z.array(z.string()).default([]),
  ancestor_capability_ids: z.array(z.string()).default([]),
  schema_version: z.literal(1),
});
```

Rules:

- Default sub-agents may not spawn children.
- Pattern E lead agents may spawn only within the granted tree budget.
- A child may not dispatch back to an ancestor capability.
- A child may not dispatch a `(capability_id, task_summary_hash)` already present in `anti_loop_keys`.
- Exhaustion blocks with `terminal_status = "denied_admission"` and `error_kind = "spawn_tree_budget_exhausted"`.
- Q dashboard shows active spawn-tree budget and descendant count.

When `may_spawn_children = true` and limits are otherwise unspecified, Pattern E preset defaults apply: `max_children_for_this_node = 5`, `max_total_descendants_remaining = 10`, inherited parent budget.

**Coordination protocols.**

- `none`: lead aggregates in its own context.
- `room`: lead and specialists communicate through a DOC12 room.
- `scratchpad`: lead and specialists use DOC12 room blackboard tools for structured shared findings.

Depth ≥ 2 requires explicit `coordination_protocol`; otherwise dispatch admission fails with `validation.pattern_e_missing_coordination_protocol`.

#### §1.7.F Pattern F: User-observable DOC12 rooms

Pattern F is for multi-agent work the user benefits from watching or steering. Elnor proposes a room before creating it. Create only after user acceptance.

Pattern F composes with foreground patterns A, B, D, E, and G. It does not automatically wrap Pattern C; background runs may post milestones to a room only when the user asked to monitor them.

A DOC12 agent room is an observable collaboration surface. It is not a DOC23 task run, Task Forum, Run Board, graph, checkpoint store, artifact index, gate, or process authority. When underlying work is a DOC23 saved task, DOC23 Run Board / Task Forum remains canonical task-run truth.

#### §1.7.G Pattern G: Verifier / Critic loop

Primary or lead produces an artifact; a critic sub-agent verifies against an explicit rubric; Elnor decides whether to revise, retry, or accept. The critic does not compose final output.

```ts
export const VerifierRubricSchema = z.object({
  rubric_id: z.string(),
  task_summary: z.string().max(500),
  criteria: z.array(z.object({
    criterion_id: z.string(),
    description: z.string().max(300),
    severity: z.enum(["blocker", "high", "medium", "low"]),
    verifier_instruction: z.string().max(1000),
  })),
  schema_version: z.literal(1),
});

export const VerifierFindingsSchema = z.object({
  rubric_id: z.string(),
  verified_at: z.string().datetime(),
  overall_assessment: z.enum([
    "pass",
    "pass_with_minor_issues",
    "needs_revision",
    "blocker",
  ]),
  findings: z.array(z.object({
    criterion_id: z.string(),
    status: z.enum(["pass", "issue", "blocker"]),
    explanation: z.string().max(1000),
    evidence_refs: z.array(z.string()).default([]),
    suggested_correction: z.string().max(2000).optional(),
  })),
  schema_version: z.literal(1),
});
```

V5.2 `VerifierFindingsSchema` is chat-native. It is not a substitute for DOC23 `EvaluationArtifactEnvelope<EvaluationResultEnvelope>` inside saved task graphs.

#### §1.7.H Pattern H: Orchestration-plan dry-run

For multi-spawn work, Elnor may request DOC24 dry-run previews before committing. Pattern H is cost transparency, not a workflow gate.

Pattern H engages only when DOC24 selected a non-task mode or when the user explicitly directed ad-hoc work. It does not engage when DOC24 selected a task-system mode.

Recommended trigger threshold: ≥5 proposed spawns or ≥$5 estimated total cost. User-requested “show me the plan first” always triggers it.

```ts
export const OrchestrationPlanSchema = z.object({
  plan_id: z.string(),
  user_goal_summary: z.string().max(500),
  proposed_spawns: z.array(z.object({
    spawn_label: z.string(),
    target: z.discriminatedUnion("kind", [
      z.object({ kind: z.literal("ad_hoc"), model_override: z.string().optional() }),
      z.object({
        kind: z.literal("registered_capability"),
        capability_id: z.string(),
        semantic_action: z.string().optional(),
      }),
    ]),
    task: z.string(),
    success_criteria: z.array(z.string()).default([]),
    context_mode: z.enum(["isolated", "fork", "sub_agent_context_pack"]),
    depends_on_spawn_labels: z.array(z.string()).default([]),
    estimated_cost_band: z.enum(["low", "medium", "high"]),
    estimated_cost_usd: z.number().optional(),
    estimated_duration_seconds: z.number().int().optional(),
  })),
  proposed_room: z.object({
    recommended: z.boolean(),
    reason: z.string().max(300),
  }).optional(),
  total_estimated_cost_usd: z.number(),
  total_estimated_duration_seconds: z.number().int(),
  risk_notes: z.array(z.string()).default([]),
  detected_task_signals: z.array(z.string()).default([]),
  detected_task_vetos: z.array(z.string()).default([]),
  task_escalation_offered: z.boolean().default(false),
  user_decision: z.enum([
    "pending",
    "approved",
    "edited",
    "rejected",
    "switched_to_task_design",
  ]).default("pending"),
  schema_version: z.literal(2),
});

export const DryRunPreviewSchema = z.object({
  dry_run_id: z.string(),
  invocation_id_simulated: z.string(),
  estimated_cost_usd: z.number(),
  estimated_duration_seconds: z.number().int(),
  estimated_input_tokens: z.number().int(),
  estimated_output_tokens: z.number().int(),
  capability_id_resolved: z.string().optional(),
  model_resolved: z.string(),
  context_mode_resolved: z.enum(["isolated", "fork", "sub_agent_context_pack"]),
  policy_decision_summary: z.string().max(500),
  warnings: z.array(z.string()).default([]),
  schema_version: z.literal(1),
});
```

**Optional SPAWNED PLANNER mode** is off by default and must be user-enabled per deployment or per turn. Planner model defaults to the same model class as Elnor, not a cheaper default. Planner runs isolated and returns an orchestration plan for Elnor to review; Elnor still decides.

### §1.8 MemoryAgent specification

MemoryAgent is a pre-registered system specialist for bounded retrieval and reasoning across ELNOR's memory and knowledge graph. Use it when the user's question benefits from specialized retrieval judgment, multi-hop graph traversal, identifying gaps, or cross-corpus memory synthesis.

Simple local graph lookups remain direct tool calls, not MemoryAgent dispatches.

Full system prompt, input/output contracts, escalation rules, and composition behavior are packaged in the EC Core system-agent package record per §1.9B.

### §1.9 DocumentIntelligenceAgent specification

DocumentIntelligenceAgent is a pre-registered system specialist for document understanding and retrieval: OCR, markdown conversion, document classification, chunking, extraction, summarization, and retrieval-support work.

Logical tool names:

- `convert_to_markdown`
- `document_classify`
- `document_chunk`
- `ocr_document`

DOC25 owns concrete tool mappings. If a required DOC25 mapping is absent, DocumentIntelligenceAgent remains registered but unavailable; DOC24 discovery marks it unavailable rather than surfacing it as invokable.

Full system prompt, input/output contracts, escalation rules, and composition behavior are packaged in the EC Core system-agent package record per §1.9B.

### §1.9A System pre-registered specialist lifecycle

System pre-registered specialists ship as versioned system packages under `system_agents/{agent_id}/agent.yaml`. EC Core writes or updates their identity records on install/upgrade through EC-owned commands; DOC72 creates or refreshes corresponding `subagent_capability` nodes.

Install-time registration registers absent bundled specialists. Upgrade reconciliation:

1. Unmodified user copy: auto-upgrade.
2. User-modified copy: preserve local override and create pending upgrade notice.
3. User may accept upgrade, keep override, or fork into personal specialist.

Users may disable system specialists for discovery and dispatch. Disabling hides from `available_subagents` and blocks ordinary dispatch but does not delete the bundled template.

Dispatch receipts record `system_package_version`, `agent_identity_version`, `capability_id`, and `capability_version`. Reputation is version-bound. Reputation from a prior system package version MAY be displayed as historical context but MUST NOT directly determine the new version's score until enough post-upgrade signals accumulate.

System-pre-registered specialists must satisfy all of:

- recurring cross-domain platform function;
- stable tool-routing or context-pack behavior;
- generic behavior without user-domain customization;
- value from system-wide utility tracking;
- bounded failure modes with explicit dispatch criteria.

Domain-specific specialists remain user-, matter-, or firm-registered by default.

### §1.9B Incorporated system specialist contracts

V5.2 supersedes V4 §1 in full. No implementation may rely on an external or archived V4 §1.8 / §1.9 as operative text.

MemoryAgent and DocumentIntelligenceAgent contracts formerly described in V4 are incorporated only as source material. Their operative runtime truth lives in EC Core package records at:

```text
system_agents/memory_agent/agent.yaml
system_agents/document_intelligence_agent/agent.yaml
```

Each package record MUST include:

```text
agent_id
display_name
description
agent_identity_version
system_prompt_ref
soul_md_ref
tool_grant_policy_ref
default_model_policy
memory_access_policy
suggested_use_cases
input_contract_ref
output_contract_ref
escalation_rules_ref
degradation_policy_ref
system_package_version
```

If V5.2 prose conflicts with the EC Core package record, the package record governs execution and the mismatch emits `system_agent_contract_drift`.

### §1.10 Composition patterns

- Pattern A + G: specialist produces or checks a bounded output.
- Pattern B + G: parallel lanes followed by verifier lane.
- Pattern E + F: lead/specialists coordinate in a room when observable coordination helps.
- Pattern C + F: background work may post milestones only if user requested monitoring.
- Pattern H + E: dry-run plans before expensive hierarchical work.
- Pattern H + DOC23: if task signals dominate, plan may become Task Agent input; Elnor does not create a hidden saved task.

### §1.10A Task Agent pre-registration

Task Agent is DOC23 Addenda B's system specialist for task design, adaptation, review, inspection, retrieval, explanation, assessment, and prompt improvement. It is pre-registered alongside MemoryAgent and DocumentIntelligenceAgent.

Task Agent is a typed-entrypoint specialist with 15 entrypoints:

```yaml
agent_id: task_agent
display_name: Task Agent
capability_id: agent.task_agent
capability_type: agent
owner_scope: system
availability_source: both
allowed_input_classes: ["*"]
semantic_actions:
  - consult_task_opportunity
  - design_task
  - adapt_task_template
  - review_existing_task
  - inspect_task_run
  - retrieve_task_output
  - explain_task_graph
  - assess_task
  - assess_task_portfolio
  - answer_task_system_question
  - improve_task_prompt
  - review_prompt_quality
  - generate_prompt_variants
  - test_prompt_variants
  - propose_prompt_update
```

Task Agent is not rendered as an ordinary registered specialist. It appears only through DOC23/DOC24 task-mode surfaces or explicit Task Agent entrypoint requests.

Task Agent's internal architecture, education substrate, learning loops, and runtime profile are owned by DOC23 Addenda B Core. V5.2 owns only the registry and dispatch compatibility substrate.

### §1.11 Monitoring and inspectability

Every sub-agent dispatch has a receipt. Q dashboard MUST make visible:

- active child runs;
- dispatch status and terminal status;
- actual model, context mode, and fork fallback;
- policy decision and exposure receipt references;
- effective tool grant;
- room routing status;
- spawn-tree id, depth, descendant count, and remaining budget;
- background work registration id for Pattern C;
- cancellation and late-after-cancel events;
- utility/reliability outcome when available.

### §1.12 Boundary with DOC23 task-module specialist dispatch

V5.2 governs Elnor's orchestration-layer dispatch. It does not govern dispatch from inside running DOC23 task modules under DOC23 Addenda A §A7.

| Concern | Owner |
|---|---|
| Elnor spawn decision | V5.2 §1.4 |
| Elnor available specialist slot | V5.2 §2.7 |
| Elnor dispatch envelope and receipt | V5.2 §2.8 series |
| Judge / ClaimExtractor specialist decision | DOC23 Addenda A §A7.3 |
| Judge specialist list | DOC23 Addenda A §A7.3 `specialist_agents` |
| Judge specialist dispatch | OpenClaw native `sessions_spawn`, not V5.2 `dispatch_to_subagent` |
| Judge specialist budget/depth | DOC23 Addenda A §A7.2 / §A7.5.2 |
| Judge specialist audit trail | DOC23 Addenda A §A7.5 and `TaskModuleSubAgentTraceSchema` |

DOC24 build-time CI MUST verify that `dispatch_to_subagent` does not appear in any task-module agent's effective tool grant. Failing validation: `validation.task_module_has_v5_dispatch_tool`. Runtime admission also blocks any caller outside Elnor-orchestration scope that attempts to use V5.2 `dispatch_to_subagent`; failing validation: `validation.dispatch_tool_not_in_caller_scope`.

---

## §2 Named Specialist Sub-Agent Capability Registry

### §2.1 Why named specialists supplement ad-hoc spawning

ELNOR is single-user. Named specialists earn their place when persistent identity provides value that ad-hoc spawning cannot:

- stable per-matter or per-domain identity;
- skill-specific budget and tool enforcement;
- reputation-tracked decision support;
- voice/persona persistence;
- hard scope or access restrictions;
- typed entrypoints with distinct output contracts or reputation partitions.

For all other cases, ad-hoc spawning from the primary model is the right choice. Named specialists are an additional option, not a replacement.

### §2.1A Typed-entrypoint capabilities

A capability may be single-purpose or typed-entrypoint.

Typed-entrypoint capabilities expose multiple semantic actions under one identity. Dispatch envelope `target.semantic_action` is required when `semantic_actions` is non-empty.

Typed-entrypoint capabilities are appropriate when actions share an identity but produce different output shapes, require per-action reputation, or benefit from grouped discovery. Task Agent is the system example. User specialists can use the same model, e.g., LegalResearchAgent with `research_edgar`, `verify_bluebook_citation`, and `extract_case_holdings`.

Per-action reputation includes `semantic_action` when present.

### §2.2 What constitutes a named specialist

A named specialist consists of:

- EC Core agent identity: who the agent is, its prompt, SOUL.md, model policy, tool grants, persona, and package version.
- DOC72 `subagent_capability` profile: what invokable capability is exposed for discovery and routing.
- DOC24 runtime hydration: current availability, health, ranking, injection slot rendering, and dispatch admission.
- BDSM/DOC8 utility: quality and reliability ledgers.

### §2.3 Agent identity vs. capability profile

EC Core owns agent identity execution truth. DOC72 stores capability profiles and cached display/discovery metadata only.

DOC72 stores:

```ts
agent_identity_ref: {
  agent_id: string;
  agent_identity_version: string;
}
```

No DOC72 `agent_identity_stub` node is operative in V5.2. Direct foreign-key reference replaces the proposed stub to avoid shadow identity truth and cache staleness.

Source-of-truth split:

| Field | Owner | DOC72 handling |
|---|---|---|
| agent_identity_id | EC Core | foreign key only |
| agent_identity_version | EC Core | copied on capability node for version join |
| display_name | EC Core | `display_name_cache` only |
| capability_id | DOC72 | canonical |
| description | EC Core/capability config | `description_cache` |
| suggested_use_cases | EC Core/capability config | `suggested_use_cases_cache` |
| owner_scope | DOC72 | canonical |
| allowed_input_classes / forbidden_input_classes | DOC72 | canonical discovery metadata |
| budgets / timeout limits | DOC72 | canonical capability profile |
| lifecycle_state | DOC72 | canonical capability lifecycle |
| health_state | runtime | hydrated, not persisted as durable truth |
| availability_state | runtime/per-dispatch | computed per context |
| quality/reliability α/β | BDSM | joined by key |

On EC Core `agent_identity_files_changed`, the DOC72 indexer refreshes only the capability-side caches: `display_name_cache`, `description_cache`, `suggested_use_cases_cache`, and `agent_identity_ref.agent_identity_version`. It does not write persona, SOUL.md, system prompt, model config, tool grants, or other EC-owned execution truth.

Versioning is independent for identity and capability. BDSM tracks reputation per `(agent_identity_id, agent_identity_version, capability_id, capability_version, semantic_action?)`.

### §2.4 Name resolution for user-directed dispatch

Resolution runs after access/scope filtering. Candidates the user cannot invoke are not considered.

Scope filter order:

1. active matter scope, if a matter is active;
2. user-private / personal scope;
3. firm-shared scope;
4. system scope.

Name fields:

- `capability_id`
- `display_name_cache`
- `aliases[]`
- EC Core `agent_identity.name`
- EC Core `agent_identity.agent_id`

Resolution order:

1. exact `capability_id`, case-insensitive;
2. exact `display_name_cache` or alias;
3. exact identity name or identity id;
4. prefix match;
5. substring match;
6. fuzzy semantic match over display name, aliases, and description cache.

Ambiguity rules:

- Exactly one exact match: dispatch.
- Multiple exact matches in different scopes: ask, unless active matter scope clearly resolves and user gave no conflicting scope hint.
- Prefix/substring: unambiguous only when best-second gap ≥ 0.15 and matched string length ≥ 4.
- Fuzzy: cosine similarity ≥ 0.82; if best-second gap < 0.10, ask.
- Below 0.82: no match; offer `list my agents`.

Elnor asks at most two disambiguation questions for the same user-directed dispatch. If still ambiguous, it presents a compact numbered list with display name, scope, and specialty summary.

Identity-level task-module resolution is separate. DOC23 Addenda A §A7 `specialist_agents[]` validation uses agent_id matching, not friendly-name fallback. Build-time lint: `validation.specialist_match_by_friendly_name_attempted`.

### §2.5 Discovery

DOC24 discovers registered specialists by:

1. filtering by lifecycle (`active` unless list-mode includes `staged` or deprecated);
2. applying access/scope filters;
3. applying current availability and policy-screened context;
4. applying input-class metadata filter: `allowed_input_classes = ["*"]` means universal permit; non-empty arrays must be a superset of current taint/input classes; empty `[]` is strict-deny by default, not "allow when no classes are present"; `forbidden_input_classes` must not intersect;
5. matching task summary to `description_cache`, `suggested_use_cases_cache`, aliases, and semantic actions;
6. ranking.

Ranking uses weighted additive score:

```text
discovery_score =
  0.40 * semantic_match_score
+ 0.25 * reputation_score
+ 0.15 * scope_fit_score
+ 0.10 * recency_score
+ 0.10 * declared_use_case_score
```

- `reputation_score`: Beta posterior mean with cold-start prior α=2, β=2.
- `scope_fit_score`: 1.0 matter, 0.85 personal/private, 0.75 firm_shared, 0.65 system, unless user requested scope.
- `recency_score`: `exp(-days_since_last_success / 60)`, default 0.50.

Discovery returns at most 12 candidates to slot rendering and omits candidates below 0.35 unless explicitly named.

If monthly token spend exceeds 80% of user threshold, an optional budget-bias penalty may reduce expensive specialists' scores and surface a non-modal budget indicator.

### §2.6 The `available_subagents` injection slot

Prompt-visible label: `available_subagents`. DOC24 registry id: `DOC24.available_subagents`.

When no specialists match, render nothing — no header, no “none available.”

When populated, render at most 600 tokens, using §2.7 template and truncation. DOC24 retains omitted candidates in packet manifest for inspector visibility.

### §2.7 Slot rendering template

Standard format:

```text
[display_name] — [one-line specialty]
  Best fit for: [1-3 concise use cases]
  Scope: [system | firm | matter | personal | private]
  Fit: [strong | good | possible] for this activation
  Reliability: [established | limited history | new]
  Invoke when: [one concise instruction]
```

The slot MUST NOT show exact acceptance percentages, raw dispatch counts, or rank scores. Those remain inspector fields.

Sort line:

```text
(sorted by relevance to this activation: semantic match + reputation + recency)
```

Multi-capability agents render grouped under one heading unless user searched a specific capability id.

Task Agent exclusion: Task Agent MUST NOT appear as an ordinary specialist. It appears only through task-mode or typed Task Agent entrypoint surfaces. Allowed Task Agent slot entries use product verbs only: Design task, Adapt task template, Review task, Inspect run, Retrieve output, Explain graph, Assess task, Improve task prompt, Test prompt variants.

Truncation:

1. Reserve header/footer and truncation notice budget.
2. Append ranked specialist blocks until next block would exceed cap.
3. Append `...and N more registered specialists not shown due to context limits.` when applicable.

### §2.8 Dispatch envelope and admission

The dispatch envelope is constructed only after Elnor has decided dispatch is appropriate. The envelope does not itself justify dispatch.

```ts
export const AgentIdentityRefSchema = z.object({
  agent_id: z.string().min(1).max(120),
  agent_identity_version: z.string().regex(/^\d+\.\d+\.\d+$/),
});

export const SubAgentDispatchEnvelopeSchema = z.object({
  invocation_id: z.string(),

  target: z.discriminatedUnion("kind", [
    z.object({
      kind: z.literal("registered_capability"),
      subagent_capability_node_id: z.string(),
      capability_id: z.string(),
      semantic_action: z.string().optional(),
    }),
    z.object({
      kind: z.literal("ad_hoc"),
      model_override: z.string().optional(),
      system_prompt_addendum: z.string().optional(),
      tool_allowlist: z.array(z.string()).optional(),
      memory_access_level: z.enum(["minimal", "standard", "full"]).default("minimal"),
    }),
  ]),

  calling_context: z.object({
    caller_doc: z.string().min(1).max(80),
    caller_section: z.string().min(1).max(80),
    intent_summary: z.string().min(1).max(500),
    coordination_point: z.string().min(1).max(80).optional(),
  }),

  task_prompt: z.string().min(1),

  scoped_context_pack: z.object({
    entity_card_refs: z.array(z.string()).default([]),
    active_constraints: z.array(z.string()).default([]),
    input_data: z.record(z.string(), z.unknown()).default({}),
    matter_id: z.string().optional(),
  }),

  room_id: z.string().optional(),
  room_output_policy: z.enum([
    "room_only",
    "room_and_parent",
    "parent_with_room_mirror",
  ]).default("parent_with_room_mirror"),

  execution_constraints: z.object({
    timeout_seconds: z.number().int().min(1).max(3600).optional(),
    max_output_tokens: z.number().int().min(1).optional(),
    max_tool_calls: z.number().int().min(0).optional(),
    retry_policy: z.object({
      max_retries: z.number().int().min(0).max(3).default(1),
      retry_on: z.array(z.enum(["timeout", "invocation_error", "schema_error"])).default([]),
      fallback_model: z.string().optional(),
      fallback_capability_id: z.string().optional(),
    }).optional(),
  }).optional(),

  cost_preference: z.object({
    prefer: z.enum(["lowest_cost", "balanced", "quality_within_budget", "fastest"]).default("balanced"),
    max_cost_usd_for_this_call: z.number().optional(),
  }).optional(),

  preferred_agents: z.object({
    agent_ids: z.array(z.string()).min(1),
    fallback_policy: z.enum(["require", "prefer"]).default("prefer"),
  }).optional(),

  per_call_budget_override: z.object({
    max_cost_usd: z.number().optional(),
    max_duration_seconds: z.number().int().optional(),
  }).optional(),
  budget_override_user_authorized: z.boolean().default(false),

  dry_run: z.boolean().default(false),

  coordination_workspace_ref: z.discriminatedUnion("kind", [
    z.object({
      kind: z.literal("doc12_room_blackboard"),
      room_id: z.string(),
      blackboard_id: z.string(),
    }),
    z.object({ kind: z.literal("none") }),
  ]).default({ kind: "none" }),

  coordination_protocol: z.enum(["none", "room", "scratchpad"]).default("none"),

  schema_version: z.literal(1),
});
```

`room_only` is permitted only when the user's goal is direct room participation rather than an Elnor-composed final answer. If Elnor is expected to synthesize or compose, use `parent_with_room_mirror` or `room_and_parent`. Admission validation: `validation.room_only_with_composing_parent`.

Budget overrides tighten by default. Loosening requires `budget_override_user_authorized = true` plus recent EC Core authorization.

Stale room recovery: if `room_id` is open at admission but closes before completion, output reroutes to Elnor's parent session with annotation: `[Output from {agent} would have gone to room {name} which was closed mid-work]`. The receipt records `room_routing_status = "room_closed_during_dispatch"`. Sub-agents MUST NOT fail solely because the room closed.

### §2.8A Dispatch admission order and receipt schema

Every dispatch flows through the following sequence:

1. Resolve target and `semantic_action`.
2. Build candidate context pack.
3. Classify data class, taint class, and exposure context.
4. Evaluate EC PolicyDecisionEngine.
5. Compute effective tool/auth grant.
6. Validate budget, room liveness, spawn-tree, retry policy, and rate limits.
7. Emit dispatch receipt at `accepted` lifecycle state.
8. Call `sessions_spawn` only if all gates allow.

Skipping or reordering gates is a runtime validation failure. DOC24 build-time CI MUST verify that each admission step has a corresponding implementation function, that dispatch entry points enforce the sequence, that stage failure emits a receipt before any later stage runs, and that no code path bypasses steps 4–7 to reach `sessions_spawn`. Validation codes: `validation.dispatch_admission_step_skipped` and `validation.dispatch_admission_step_out_of_order`.

```ts
export const SubAgentDispatchReceiptSchema = z.object({
  receipt_id: z.string(),
  invocation_id: z.string(),
  idempotency_key: z.string().optional(),

  parent_session_id: z.string(),
  parent_agent_id: z.string(),
  child_session_key: z.string().optional(),
  openclaw_run_id: z.string().optional(),

  target: z.discriminatedUnion("kind", [
    z.object({
      kind: z.literal("registered_capability"),
      subagent_capability_node_id: z.string(),
      capability_id: z.string(),
      capability_version: z.string(),
      agent_identity_id: z.string(),
      agent_identity_version: z.string(),
      system_package_version: z.string().optional(),
      semantic_action: z.string().optional(),
    }),
    z.object({
      kind: z.literal("ad_hoc"),
      model_override: z.string().optional(),
      system_prompt_addendum_hash: z.string().optional(),
    }),
  ]),

  receipt_lifecycle_state: z.enum(["accepted", "running", "terminal"]).default("accepted"),
  terminal_status: z.enum([
    "completed",
    "completed_with_validation_errors",
    "partial_result_available",
    "policy_blocked",
    "cancelled_by_user",
    "cancelled_by_parent_stop",
    "timed_out",
    "failed_invocation",
    "failed_output_validation",
    "failed_runtime",
    "denied_admission",
    "late_after_cancel",
  ]).optional(),

  requested_context_mode: z.enum(["isolated", "fork", "sub_agent_context_pack"]),
  actual_context_mode: z.enum(["isolated", "fork", "sub_agent_context_pack"]),
  fork_fallback_reason: z.enum([
    "transcript_oversize",
    "policy_denied",
    "feature_unavailable",
  ]).optional(),

  model_requested: z.string().optional(),
  model_used: z.string().optional(),
  model_class: z.enum(["cheap_local", "cheap_api", "medium", "expensive_frontier"]).optional(),
  model_fingerprint: z.string().optional(),

  policy_generation_id: z.string(),
  policy_decision_ref: z.string().optional(),
  exposure_receipt_ref: z.string().optional(),
  effective_tool_grant_ref: z.string().optional(),

  room_id: z.string().optional(),
  room_output_policy: z.enum(["room_only", "room_and_parent", "parent_with_room_mirror"]).optional(),
  room_routing_status: z.enum(["routed", "room_closed_during_dispatch", "not_applicable"]).default("not_applicable"),
  room_post_receipt_refs: z.array(z.string()).default([]),

  output_ref: z.string().optional(),
  output_summary: z.string().max(2000).optional(),
  output_contract_refs: z.array(z.string()).default([]),
  output_validation_status: z.enum(["not_required", "passed", "failed", "partial"]).default("not_required"),
  output_validation_error_ref: z.string().optional(),
  output_taint_class: z.enum([
    "system_trusted", "user_trusted_bounded", "user_advisory",
    "internal_corpus_trusted", "external_authority_trusted",
    "external_untrusted", "adversarial_known", "unclassified",
  ]).default("unclassified"),

  usage: z.object({
    prompt_tokens: z.number().int().nonnegative().optional(),
    completion_tokens: z.number().int().nonnegative().optional(),
    total_tokens: z.number().int().nonnegative().optional(),
    cost_usd: z.number().nonnegative().optional(),
    latency_ms: z.number().int().nonnegative().optional(),
    tool_call_count: z.number().int().nonnegative().optional(),
  }).default({}),

  error: z.object({
    error_kind: z.enum([
      "policy_blocked", "tool_grant_denied", "model_unavailable",
      "agent_identity_unavailable", "capability_unavailable",
      "timeout", "schema_validation_failed", "output_contract_failed",
      "coordination_point_unauthorized", "openclaw_invocation_error",
      "runtime_error", "spawn_tree_budget_exhausted", "unknown",
    ]),
    message: z.string().max(1000),
    retryable: z.boolean().default(false),
  }).optional(),

  retry_chain: z.array(z.object({
    attempt_invocation_id: z.string(),
    terminal_status: z.string(),
    error_kind: z.string().optional(),
  })).default([]),

  spawn_tree_id: z.string().optional(),
  spawn_tree_depth: z.number().int().min(0).max(5).optional(),

  started_at: z.string().datetime(),
  completed_at: z.string().datetime().optional(),
  schema_version: z.literal(1),
});
```

`terminal_status` is required when `receipt_lifecycle_state = "terminal"`. Blocked pre-spawn dispatches still emit terminal receipts.

### §2.8B Policy and exposure gate

Every sub-agent dispatch is a context exposure event. DOC24 MUST evaluate through EC Core's compiled `PolicyDecisionEngine` before `sessions_spawn`.

```ts
export const SubAgentExposureContextSchema = z.object({
  exposure_id: z.string(),
  invocation_id: z.string(),
  destination: z.enum([
    "same_machine_local_runtime",
    "cloud_api",
    "agent_messaging",
    "firm_server",
    "remote_peer",
  ]),
  interaction_mode: z.enum(["interactive", "background_non_interactive"]),
  exposure_context: z.enum([
    "automatic_packet_injection",
    "explicit_memory_attach",
    "user_authored_prompt",
    "sub_agent_context_pack",
    "forked_transcript",
    "tool_result_forwarding",
  ]),
  data_class_summary: z.array(z.string()).default([]),
  taint_class_summary: z.array(z.string()).default([]),
  matter_id: z.string().optional(),
  policy_generation_id: z.string(),
  policy_result: z.enum(["allow", "warn", "block"]),
  warning_ack_ref: z.string().optional(),
  policy_decision_ref: z.string(),
  excluded_context_refs: z.array(z.string()).default([]),
  exposure_receipt_required: z.boolean().default(true),
  effective_runtime_state_ref: z.string(),
  effective_runtime_gating: z.object({
    pause_all_processing: z.boolean(),
    pause_llm_processing_only: z.boolean(),
    global_incognito: z.boolean(),
    chat_incognito: z.boolean(),
    memory_application_enabled: z.boolean(),
    learning_collection_enabled: z.boolean(),
  }),
  schema_version: z.literal(1),
});
```

Rules:

1. `policy_result = "block"`: do not spawn; emit terminal policy-blocked receipt.
2. `policy_result = "warn"`: proceed only with `warning_ack_ref` or authorized override.
3. Pause-all-processing blocks background and non-essential dispatch.
4. Pause-LLM-processing blocks LLM-backed dispatch unless explicitly user-requested interactive action is allowed.
5. Incognito permits only user-authored prompt text and explicitly attached context; no automatic memory expansion.
6. Memory application disabled: no DOC72/DOC1 context pack assembly.
7. Collection/learning disabled: BDSM learning signals are suppressed or non-learning telemetry per EC policy.

### §2.8C Effective tool and auth grant computation

Before dispatch, DOC24 computes an effective grant as the intersection of:

1. OpenClaw native tool eligibility;
2. EC Core agent identity grants;
3. capability profile tool/capability limits;
4. caller `tool_allowlist`;
5. EC PolicyDecisionEngine exposure and side-effect rules;
6. user cost/runtime settings;
7. active matter/workspace policy.

```ts
export const SubAgentEffectiveToolGrantSchema = z.object({
  grant_id: z.string(),
  invocation_id: z.string(),
  target_agent_identity_id: z.string().optional(),
  target_capability_id: z.string().optional(),
  requested_tools: z.array(z.string()).default([]),
  identity_declared_tools: z.array(z.string()).default([]),
  capability_allowed_tools: z.array(z.string()).default([]),
  caller_tool_allowlist: z.array(z.string()).default([]),
  granted_tools: z.array(z.string()).default([]),
  denied_tools: z.array(z.object({
    tool_id: z.string(),
    reason_code: z.string(),
  })).default([]),
  auth_profiles_requested: z.array(z.string()).default([]),
  auth_profiles_granted: z.array(z.string()).default([]),
  auth_profiles_denied: z.array(z.object({
    auth_profile_id: z.string(),
    reason_code: z.string(),
  })).default([]),
  side_effect_policy: z.enum([
    "no_side_effects",
    "draft_only",
    "approval_required",
    "allowed_with_receipts",
  ]).default("no_side_effects"),
  policy_decision_ref: z.string(),
  created_at: z.string().datetime(),
  schema_version: z.literal(1),
});
```

Default ad-hoc sub-agents receive no side-effecting tools unless explicitly granted. Side effects include external sends, file writes outside workspace, calendar writes, webhook posts, filing/submission actions, and memory writes.

### §2.8D Sub-agent output taint and output-contract validation

Sub-agent output is advisory evidence, not authority.

```ts
export const SubAgentOutputHandlingSchema = z.object({
  output_handling_id: z.string(),
  dispatch_receipt_id: z.string(),
  output_ref: z.string(),
  output_contract_refs: z.array(z.string()).default([]),
  taint_class: z.enum([
    "system_trusted", "user_trusted_bounded", "user_advisory",
    "internal_corpus_trusted", "external_authority_trusted",
    "external_untrusted", "adversarial_known", "unclassified",
  ]).default("unclassified"),
  contains_instructions_to_parent: z.boolean().default(false),
  instruction_handling: z.enum([
    "ignored",
    "summarized_as_content",
    "requires_user_review",
    "blocked",
  ]).default("ignored"),
  contract_validation: z.object({
    required: z.boolean().default(false),
    status: z.enum(["not_required", "passed", "failed", "partial"]).default("not_required"),
    validator_ref: z.string().optional(),
    error_ref: z.string().optional(),
  }),
  verification_recommended: z.boolean().default(false),
  verifier_dispatch_receipt_id: z.string().optional(),
  schema_version: z.literal(1),
});
```

Instructions inside sub-agent output are content, not parent instructions. High-stakes failed or partial validation blocks automatic incorporation into final composition.

### §2.8E Sub-agent contribution trace

When sub-agent output materially influences Elnor's final response, Elnor SHOULD emit a contribution trace.

```ts
export const SubAgentContributionTraceSchema = z.object({
  contribution_trace_id: z.string(),
  parent_session_id: z.string(),
  final_response_ref: z.string().optional(),
  dispatch_receipt_id: z.string(),
  output_handling_id: z.string().optional(),
  taint_class: z.enum([
    "system_trusted", "user_trusted_bounded", "user_advisory",
    "internal_corpus_trusted", "external_authority_trusted",
    "external_untrusted", "adversarial_known", "unclassified",
  ]).optional(),
  subagent_capability_node_id: z.string().optional(),
  agent_identity_id: z.string().optional(),
  contribution_kind: z.enum([
    "source_found",
    "citation_verified",
    "fact_extracted",
    "analysis_component",
    "draft_component",
    "critique_or_redteam",
    "disagreement_considered",
    "not_used",
  ]),
  used_in_final: z.boolean(),
  use_summary: z.string().max(1000),
  evidence_refs: z.array(z.string()).default([]),
  user_visible_disclosure: z.enum(["none", "brief_attribution", "full_agent_list"]).default("none"),
  created_at: z.string().datetime(),
  schema_version: z.literal(2),
});
```

User-visible disclosure defaults to none; brief attribution is appropriate for high-stakes, citation-heavy, multi-agent, or user-requested work.

### §2.8F Cancellation, partial results, and retry

If user cancels a parent request, DOC24 sends cancellation to all active child runs created for that request. Every child must produce a terminal receipt.

Timing:

- Child completed before cancellation: `completed`.
- Child completed after cancellation but within 2s grace window: `completed`.
- Child completed after grace window: `late_after_cancel`; do not auto-incorporate.
- Child stopped due to cancel: `cancelled_by_user` or `cancelled_by_parent_stop`.

If some lanes complete and others fail/time out, Elnor may use completed results only after disclosing incompleteness when it materially affects confidence.

Retry is allowed once only when policy, budget, retry policy, side-effect policy, and user authorization allow it. No automatic retry of side-effecting work.

### §2.9 Reputation and learning

BDSM tracks advice quality separately from infrastructure reliability.

Quality weights:

- `advice_accepted` → quality α += 1.0
- `advice_partially_accepted_helpful` → α += 0.7, β += 0.3
- `advice_partially_accepted_unhelpful` → α += 0.3, β += 0.7
- `advice_rejected_correctly` → β += 1.0
- `advice_caused_regression` → β += 2.0
- `advice_deferred` → pending, no α/β update
- `advice_deferred_later_used` → α += 0.7
- `advice_deferred_expired` → β += 0.3

Reliability weights:

- `dispatch_failed_infrastructure` → reliability β += 0.5
- `dispatch_timed_out` → reliability β += 0.75
- `dispatch_blocked_policy` → no signal

Weights are initial calibrations and SHOULD be configurable in EC Core / BDSM tuning surfaces.

Discovery reputation uses advice-quality α/β. Dispatch scheduling may additionally consider infrastructure reliability, especially repeated timeouts or invocation failures. Mechanical failures MUST NOT be treated as advice-quality failures.

Deferred advice resolution: `advice_deferred` opens a pending utility record, not a terminal quality outcome. If the deferred advice is later used in a final answer, saved template, artifact revision, or user-approved action, BDSM converts it to `advice_deferred_later_used`. If it remains unused past the configured TTL, BDSM converts it to `advice_deferred_expired`. Deferred advice MUST NOT update quality α/β until resolved.

### §2.9A Automatic retry on failure or low utility

When `SubAgentDispatchReceipt.terminal_status` indicates `failed_invocation`, `failed_runtime`, or `timed_out`, or when a completed dispatch receives BDSM utility score below 0.4, the primary MAY retry once under the dispatch envelope's `retry_policy`. Retry is allowed only if policy, budget, side-effect policy, and user authorization permit it.

Permitted retry shapes:

- same task, different registered specialist if available and fit/reputation justify it;
- same specialist with fallback model when the fallback is same-class or cheaper/faster and policy permits;
- ad-hoc spawn with focused `system_prompt_addendum`: `Focus only on the core request; previous attempt failed due to [error_kind].`

Maximum one retry per original dispatch. Retry chains record every attempt in `SubAgentDispatchReceipt.retry_chain`. Retry is transparent to the user unless both attempts fail or incompleteness materially affects confidence. Side-effecting work MUST NOT be retried automatically without authorization.

### §2.10 Handling multiple agents with similar specialties

When multiple specialists match, Elnor should prefer exact user-directed scope, semantic fit, and current-context availability. Reputation breaks ties but does not override bad fit. If user intent is ambiguous, ask or present a numbered list.

### §2.11 Lifecycle: registration, deprecation, retirement

Capability lifecycle states:

- `staged`: registered but omitted from default routing slots;
- `active`: available for discovery and dispatch if runtime state permits;
- `deprecated`: visible in Agents page and list mode, omitted from default dispatch;
- `retired`: retained for audit/history, not dispatchable.

Runtime state layers:

```text
lifecycle_state    durable DOC72 capability state
health_state       upstream runtime health independent of current request
availability_state current-context availability after policy, scope, model, tools, budget, lockout
```

### §2.12 Optional provider advisor integration

Provider-specific advisor integration is optional adapter capability, not core orchestration. The standing prompt does not mention it.

```ts
export const ProviderAdvisorCapabilitySchema = z.object({
  provider_id: z.string(),
  model_family: z.string(),
  advisor_supported: z.boolean(),
  advisor_mode: z.enum([
    "api_advisor_header",
    "agent_handoff_primitive",
    "hierarchical_agent",
    "none",
  ]),
  source_ref: z.string(),
  last_verified_at: z.string().datetime(),
  schema_version: z.literal(1),
});
```

When unsupported, V5.2 patterns operate unchanged. Pattern G remains the cross-provider quality-gating equivalent.

---

## §3 Cross-Doc Implementation Addendum

### §3.1 DOC72 — `subagent_capability` node kind

OwnerScope:

```ts
const OwnerScopeSchema = z.discriminatedUnion("scope_kind", [
  z.object({ scope_kind: z.literal("system") }),
  z.object({ scope_kind: z.literal("firm_shared"), firm_id: z.string().optional() }),
  z.object({ scope_kind: z.literal("matter_scoped"), matter_id: z.string() }),
  z.object({ scope_kind: z.literal("personal"), user_id: z.string() }),
  z.object({
    scope_kind: z.literal("private"),
    user_id: z.string(),
    private_scope_ref: z.string().optional(),
  }),
]);
```

Scope normalization:

| DOC24 SpaceKind | V5.2 owner scope | DOC72 KnowledgeNodeScope |
|---|---|---|
| system | system | system |
| firm_shared | firm_shared | firm_shared |
| matter | matter_scoped | matter_scoped |
| personal | personal | personal |
| private | private | private |

Capability payload:

```ts
export const SemanticActionSpecSchema = z.object({
  display_label: z.string().min(1).max(120),
  description: z.string().max(500).optional(),
  output_contract_refs: z.array(z.string()).default([]),
  allowed_tool_ids: z.array(z.string()).default([]),
  allowed_input_classes: z.array(z.string()).default([]),
  forbidden_input_classes: z.array(z.string()).default([]),
  default_cost_preference: z.enum([
    "lowest_cost",
    "balanced",
    "quality_within_budget",
    "fastest",
  ]).optional(),
});

export const SubAgentCapabilityPayloadSchema = z.object({
  agent_identity_ref: AgentIdentityRefSchema,
  capability_id: z.string().min(1).max(120),
  capability_type: z.enum(["agent", "tool"]).default("agent"),
  semantic_actions: z.array(z.string().min(1).max(80)).default([]),
  semantic_action_specs: z.record(z.string(), SemanticActionSpecSchema).optional(),
  availability_source: z.enum([
    "ec_agent_registry",
    "doc11_named_agent_runtime_truth",
    "both",
  ]).default("ec_agent_registry"),
  health_state: z.enum(["healthy", "degraded", "unavailable", "unknown"]).default("unknown"),
  display_name_cache: z.string().min(1).max(200),
  aliases: z.array(z.string().min(1).max(80)).max(20).default([]),
  version: z.string().regex(/^\d+\.\d+\.\d+$/),
  description_cache: z.string(),
  suggested_use_cases_cache: z.array(z.string()).default([]),
  owner_scope: OwnerScopeSchema,
  allowed_input_classes: z.array(z.string()).default([]),
  forbidden_input_classes: z.array(z.string()).default([]),
  allowed_coordination_points: z.array(z.string().min(1).max(80)).default([]),
  output_contract_refs: z.array(z.string()).min(1),
  budgets: z.object({
    max_cost_usd_per_call: z.number().nonnegative().optional(),
    default_timeout_seconds: z.number().int().positive().optional(),
  }).default({}),
  lifecycle_state: z.enum(["staged", "active", "deprecated", "retired"]).default("active"),
  schema_version: z.literal(2),
});
```

Validation:

- `semantic_action_specs` keys must be in `semantic_actions`.
- aliases are unique per principal after lowercase/trim normalization; collisions are ambiguous unless active matter scope resolves.
- If `semantic_actions` is non-empty, dispatch admission requires `target.semantic_action`; missing value fails with `validation.dispatch_envelope_missing_semantic_action`.
- If supplied `target.semantic_action` is not in `semantic_actions`, admission fails with `validation.dispatch_envelope_unauthorized_semantic_action`.
- `allowed_coordination_points` requires envelope `calling_context.coordination_point` match when non-empty; absent or unauthorized values fail with `validation.dispatch_envelope_coordination_point_unauthorized`.
- build-time scope vocabulary mapping must be lossless across V5.2, DOC24, and DOC72; drift fails with `validation.scope_vocabulary_drift`.

### §3.2 DOC24 — registry runtime, slot, dispatch

DOC24 hydrates `RegisteredSubAgent` from DOC72 capability profiles, EC Core identities, DOC11 runtime truth, and BDSM reputation.

```ts
availability_state: z.enum([
  "available",
  "policy_blocked_for_context",
  "tool_unavailable",
  "model_unavailable",
  "agent_identity_unavailable",
  "system_package_stale",
  "failure_lockout",
  "disabled_by_user",
]).default("available"),

availability_reason_codes: z.array(z.string().max(120)).default([]),
```

DOC24 InjectionSlotRegistry entry:

```ts
{
  slot_id: "DOC24.available_subagents",
  owner_doc: "DOC24",
  slot_name: "available_subagents",
  slot_kind: "tool_capability_block",
  expected_rendering_constraints: [
    {
      surface: "prompt_inline",
      allowed_kinds: ["compact"],
      pii_redaction_required: true,
      schema_version: 1
    },
    {
      surface: "ui_inspector",
      allowed_kinds: ["full", "compact", "reference_only"],
      pii_redaction_required: true,
      schema_version: 1
    }
  ],
  is_required_for_packet_kind: [
    {
      packet_kind: "assistant_turn",
      required: false,
      rationale: "Only populated when DOC24 discovers materially relevant registered specialists for the current activation."
    },
    {
      packet_kind: "structured_ec_action",
      required: false,
      rationale: "Structured actions may dispatch directly when the caller already names a capability."
    },
    {
      packet_kind: "background_extraction",
      required: false,
      rationale: "Background extraction should not receive prompt-visible specialist lists unless explicitly routed through sub-agent orchestration."
    }
  ],
  schema_version: 1
}
```

### §3.3 EC Core — agent identity and command closure

EC Core owns agent identity, system-agent package records, durable writes, effective runtime state, policy evaluation, command closure, and route registry.

Agents page and chat-driven agent creation are not buildable until EC exposes command closure:

```text
AgentCreateCommand
AgentUpdateCommand
AgentDeprecateCommand
AgentRetireCommand
AgentForkCommand
SubAgentCapabilityCreateCommand
SubAgentCapabilityUpdateCommand
SubAgentCapabilityDeprecateCommand
SubAgentCapabilityRetireCommand
SubAgentPackMountCommand
SubAgentPackUnmountCommand
SubAgentFailureLockoutClearCommand
SubAgentMaintenanceSuggestionDismissCommand
```

Chat-driven creation requires preview and user approval before durable writes to identity, SOUL.md, system prompt, tool grants, or capability profile. Text from documents, web pages, emails, or other untrusted sources MUST NOT be written into persistent agent instructions unless the user explicitly approves the exact instruction text.

### §3.4 BDSM / DOC8 — sub-agent utility ledger

```ts
export const SubAgentUtilitySignalSchema = z.object({
  signal_id: z.string(),
  dispatch_receipt_id: z.string().optional(),
  agent_identity_id: z.string(),
  agent_identity_version: z.string(),
  capability_id: z.string(),
  capability_version: z.string(),
  semantic_action: z.string().optional(),
  outcome: z.enum([
    "advice_accepted",
    "advice_partially_accepted_helpful",
    "advice_partially_accepted_unhelpful",
    "advice_rejected_correctly",
    "advice_caused_regression",
    "advice_deferred",
    "advice_deferred_later_used",
    "advice_deferred_expired",
    "dispatch_failed_infrastructure",
    "dispatch_timed_out",
    "dispatch_blocked_policy",
  ]),
  learning_suppressed: z.boolean().default(false),
  learning_suppression_reason_codes: z.array(z.string()).default([]),
  effective_runtime_state_ref: z.string().optional(),
  learning_visibility_scope: z.object({
    scope_kind: z.enum(["matter_scoped", "firm_shared", "personal", "private", "system"]),
    principal_id: z.string(),
    scope_inference_basis: z.string().optional(),
    pbe_corpus_refs: z.array(z.string()).default([]),
  }).passthrough(),
  created_at: z.string().datetime(),
  schema_version: z.literal(2),
});
```

Ledger:

```ts
SubAgentUtilityLedger {
  agent_identity_id: string
  agent_identity_version: string
  capability_id: string
  capability_version: string
  semantic_action?: string
  quality_alpha: number
  quality_beta: number
  last_quality_signal_at: string
  reliability_alpha: number
  reliability_beta: number
  last_reliability_signal_at: string
  invocation_count: number
  accepted_count: number
  partially_accepted_helpful_count: number
  partially_accepted_unhelpful_count: number
  rejected_count: number
  deferred_pending_count: number
  deferred_later_used_count: number
  deferred_expired_count: number
  regression_count: number
  timeout_count: number
  infrastructure_failure_count: number
  cost_total_tokens: number
  cost_total_usd: number
  mean_latency_ms: number
  schema_version: 2
}
```

Forward compatibility: BDSM v6.5+ targets a unified specialist reputation read combining V5.2 Elnor dispatches and DOC23 Addenda A §A7 task-module specialist traces.

### §3.5 DOC20 — Agents page specification

The Agents page is a read/control surface. It must not directly write durable truth. Every visible control maps to EC command, explicit no-op, degraded path, telemetry, and refreshed read model.

Minimum read models:

```text
AgentsListReadModel
AgentDetailReadModel
SubAgentCapabilityListReadModel
AgentDispatchHistoryReadModel
SubAgentReputationReadModel
SubAgentPackMountReadModel
```

Agents page shows lifecycle, health, availability, scope, aliases, semantic actions, tool grants, recent receipts, utility bands, failure lockouts, and policy/availability reason codes.

### §3.6 DOC23 compatibility and task-module integration

DOC23-originated terms map into V5.2 as follows:

| DOC23 term | V5.2 canonical home |
|---|---|
| `AdvisorySubAgentProfile` | EC Core `agent_identity` + DOC72 `subagent_capability` |
| `specialty_description` | EC Core/capability description cached as DOC72 `description_cache` |
| `anticipated_use_cases` | EC Core suggested use cases cached as DOC72 `suggested_use_cases_cache` |
| `soul_md_ref` / `system_prompt_ref` | EC Core agent identity only |
| `skills` | EC Core agent identity; capability may expose summary |
| `learning_policy` | BDSM/DOC8 utility ledger and learning-visibility controls |
| `allowed_coordination_points` | V5.2 capability profile, not dropped |
| `available_sub_agents` | compatibility alias for DOC24 `available_subagents` |
| `dispatched_at` / `dispatched_for` | `SubAgentDispatchReceipt` |
| `output_received` | `SubAgentDispatchReceipt.output_ref` |

DOC23 Addenda B Outcome Evaluator/Revisor next-minor migration should add `agent_identity_ref`, point `learning_policy_handle` to BDSM, and move `allowed_coordination_points` to capability profile.

Task-module sub-agent trace:

```ts
export const TaskModuleSubAgentTraceSchema = z.object({
  trace_id: z.string(),
  task_id: z.string(),
  run_id: z.string(),
  module_id: z.string(),
  activation_seq: z.number().int(),
  runtime_spawn_receipt_ref: z.string(),
  runtime_spawn_receipt_kind: z.enum([
    "v5_elnor_dispatch_receipt",
    "doc23_task_subagent_trace",
    "doc11_openclaw_spawn_receipt",
  ]),
  subagent_capability_node_id: z.string().optional(),
  agent_identity_id: z.string().optional(),
  purpose_summary: z.string().max(500),
  input_artifact_refs: z.array(z.string()).default([]),
  output_artifact_refs: z.array(z.string()).default([]),
  policy_snapshot_ref: z.string().optional(),
  doc24_context_packet_ref: z.string().optional(),
  policy_decision_ref: z.string().optional(),
  exposure_receipt_ref: z.string().optional(),
  effective_tool_grant_ref: z.string().optional(),
  output_taint_class: z.enum([
    "system_trusted", "user_trusted_bounded", "user_advisory",
    "internal_corpus_trusted", "external_authority_trusted",
    "external_untrusted", "adversarial_known", "unclassified",
  ]).optional(),
  output_validation_status: z.enum([
    "not_required", "passed", "failed", "partial",
  ]).optional(),
  status: z.enum([
    "completed", "partial_result_available", "policy_blocked",
    "cancelled", "timed_out", "failed",
  ]),
  visible_in_run_inspector: z.boolean().default(true),
  schema_version: z.literal(2),
});
```

### §3.7 DOC12 — agent room tools and room blackboard

DOC12 exposes:

```text
create_agent_room(name: string, description?: string, agent_ids?: string[]) → room_id
post_to_room(room_id: string, message: string, attributed_to?: string)
post_finding_to_room(room_id, key, summary, payload, taint_class, visibility) → FindingPostReceipt
query_room_findings(room_id, query?) → Record<string, RoomBlackboardFinding>
```

Room lifecycle:

- Elnor proposes before creating; create only on acceptance.
- Decline memory records `{parent_session_id, workstream_label}` to avoid repeated offers.
- Closing a room mid-work stops future room posts; child runs continue to parent unless parent stopped.
- Parent stop cancels children; room persists as archived transcript.
- A later session may reopen transcript but active participation requires new dispatch.
- @mentions route through DOC10. Inactive registered specialists require explicit spawn proposal, not silent auto-spawn.

DOC12 room retention defaults are configurable. Baseline defaults: 30 days for active rooms and 90 days for archived rooms. Room persistence never implies sub-agent persistence.

Room blackboard finding:

```ts
export const RoomBlackboardFindingSchema = z.object({
  finding_id: z.string(),
  room_id: z.string(),
  blackboard_id: z.string(),
  key: z.string().min(1).max(120),
  summary: z.string().max(2000),
  payload_ref: z.string().optional(),
  taint_class: z.enum([
    "system_trusted", "user_trusted_bounded", "user_advisory",
    "internal_corpus_trusted", "external_authority_trusted",
    "external_untrusted", "adversarial_known", "unclassified",
  ]),
  data_class_summary: z.array(z.string()).default([]),
  visible_to_user: z.boolean().default(true),
  visible_to_agents: z.union([
    z.array(z.string()),
    z.literal("all_room_participants"),
  ]).default("all_room_participants"),
  expires_at: z.string().datetime().optional(),
  source_dispatch_receipt_id: z.string(),
  posted_by_agent_id: z.string(),
  posted_at: z.string().datetime(),
  schema_version: z.literal(1),
});
```

Query returns only findings visible to the querying agent and permitted by that agent's exposure profile.

### §3.8 DOC25 — document intelligence tools

DocumentIntelligenceAgent logical tools map to DOC25 primitives. Missing mapping means unavailable specialist, not best-effort unsafe dispatch. Each tool must declare model/local/cloud exposure constraints.

### §3.9 DOC10 — room-message routing

DOC10 recognizes DOC12 room user messages as engagement events. Routing rules:

- `@AgentName` to active room participant routes to that agent.
- `@AgentName` inactive but registered creates explicit spawn/dispatch proposal.
- ambiguous names use §2.4 disambiguation.
- unmentioned messages route according to room participation policy.
- room closure and parent-stop update active-run tracking.

---

## §4 OP-A row additions and updates (V5.2)

```yaml
OBL-EC-NEW-SUBAGENT-STANDING-ORDER-V52-01:
  target_spec: EC_Core_Addendum_A
  target_section: SOUL.md / standing-orders prompt assembly
  source: V5.2 §1.4
  requirement_type: REQ
  status: MISSING
  acceptance:
    - V5.2 §1.4 canonical prompt injected
    - tokenizer CI enforces ≤450 token limit per deployed tokenizer
    - static orchestration layer cacheability verified

OBL-D24-NEW-SUBAGENT-DISPATCH-01:
  target_spec: DOC24
  target_section: sub-agent dispatch admission / runtime invocation
  source: V5.2 §2.8 series
  requirement_type: REQ
  status: MISSING
  acceptance:
    - SubAgentDispatchEnvelopeSchema published
    - Dispatch admission order implemented in sequence
    - SubAgentDispatchReceiptSchema emitted for accepted, blocked, failed, cancelled, and timed-out dispatches
    - EC/PropA policy gate evaluated before sessions_spawn
    - effective tool/auth grant computed before sessions_spawn
    - output taint and validation records emitted

OBL-D24-NEW-AVAILABLE-SUBAGENTS-SLOT-01:
  target_spec: DOC24
  target_section: InjectionSlotRegistry
  source: V5.2 §2.6 + §3.2
  requirement_type: REQ
  status: MISSING
  acceptance:
    - DOC24.available_subagents registered with DOC24 R3.1.1 InjectionSlotRegistry shape
    - empty slot renders nothing
    - slot rendering follows §2.7 template and 600-token cap

OBL-D72-NEW-SUBAGENT-NODE-KIND-01:
  target_spec: DOC72
  target_section: node taxonomy / capability profile payloads
  source: V5.2 §3.1
  requirement_type: REQ
  status: MISSING
  acceptance:
    - subagent_capability node kind added
    - SubAgentCapabilityPayloadSchema V2 added
    - no agent_identity_stub node required
    - display_name_cache not treated as execution truth
    - scope vocabulary maps to DOC24 and DOC72

OBL-D72-AGENT-IDENTITY-STUB-CACHE-CONTRACT-01:
  target_spec: DOC72
  target_section: subagent_capability node hydration / identity cache refresh
  source: V5.2 §2.3 + §3.1
  requirement_type: REQ
  status: MISSING
  acceptance:
    - row name retained for card compatibility; implementation uses agent_identity_ref, not a stub node
    - agent_identity_ref refreshed from EC Core on agent_identity_files_changed
    - display_name_cache, description_cache, and suggested_use_cases_cache refreshed in same indexer pass
    - EC Core remains sole writer; DOC72 never writes execution identity truth

OBL-D24-TASK-AGENT-REGISTRATION-01:
  target_spec: DOC24
  target_section: RegisteredSubAgent hydration / pre-registered system specialists
  source: V5.2 §1.10A
  requirement_type: REQ
  status: MISSING
  depends_on:
    - OBL-D72-NEW-SUBAGENT-NODE-KIND-01
    - OBL-D24-NEW-SUBAGENT-DISPATCH-01
  acceptance:
    - Task Agent registered with capability_id agent.task_agent
    - semantic_actions populated with 15 DOC23 entrypoints
    - lifecycle_state staged until DOC23 implementation is operative
    - Task Agent excluded from ordinary available_subagents rendering

OBL-D10-NEW-ROOM-AGENT-ROUTING-01:
  target_spec: DOC10
  target_section: engagement orchestrator / room-message routing
  source: V5.2 §3.9
  requirement_type: REQ
  status: MISSING
  depends_on:
    - OBL-D12-NEW-AGENT-ROOM-TOOLS-01
  blocks:
    - OBL-D24-NEW-SUBAGENT-DISPATCH-01
  acceptance:
    - DOC10 recognizes DOC12 room user messages
    - @mentions route to active named agents
    - inactive registered agents require explicit spawn proposal
    - ambiguous @mentions trigger §2.4 disambiguation

OBL-D12-NEW-ROOM-BLACKBOARD-01:
  target_spec: DOC12
  target_section: room tools / room blackboard
  source: V5.2 §3.7
  requirement_type: REQ
  status: MISSING
  acceptance:
    - post_finding_to_room and query_room_findings exposed
    - RoomBlackboardFindingSchema implemented
    - taint, data class, visibility, expiry, source receipt governance enforced

OBL-D12-NEW-AGENT-ROOM-TOOLS-01_UPDATE:
  target_spec: DOC12
  target_section: agent room tools existing row update
  source: V5.2 §3.7 + ADJ-009 / ADJ-022
  requirement_type: REQ
  status: UPDATE
  blocks:
    - OBL-D10-NEW-ROOM-AGENT-ROUTING-01
    - OBL-D24-NEW-SUBAGENT-DISPATCH-01
  acceptance:
    - existing DOC12 room-tools obligation explicitly blocks DOC10 room-message routing
    - room closure events propagate to DOC10 active-run tracking

OBL-D25-NEW-DOCUMENT-INTELLIGENCE-TOOLS-01:
  target_spec: DOC25
  target_section: document intelligence tool registry / source ingestion
  source: V5.2 §1.9 + §3.8
  requirement_type: REQ
  status: MISSING
  acceptance:
    - convert_to_markdown, document_classify, document_chunk, ocr_document map to DOC25 primitives
    - each tool declares exposure constraints
    - DocumentIntelligenceAgent dispatch fails closed if mappings absent

OBL-EC-NEW-AGENT-COMMAND-CLOSURE-01:
  target_spec: EC_Core_Addendum_A
  target_section: agent identity registry commands / route registry
  source: V5.2 §3.3 + §3.5
  requirement_type: REQ
  status: MISSING
  depends_on:
    - OBL-EC-NEW-AGENT-CONFIG-SUGGESTED-USE-CASES-01
  blocks:
    - OBL-D20-NEW-AGENTS-PAGE-01
  acceptance:
    - agent identity commands published
    - capability profile commands published
    - pack mount/unmount commands published
    - failure lockout clear command published
    - every Agents page control maps to command/no-op/degraded path
    - chat-driven creation requires preview and user approval

OBL-EC-NEW-SUBAGENT-BACKGROUND-WORK-REGISTRY-01:
  target_spec: EC_Core_Addendum_A
  target_section: background orchestrator / background work registry
  source: V5.2 §1.7.C
  requirement_type: REQ
  status: MISSING
  acceptance:
    - Pattern C dispatches register with EC background work registry
    - processing pause/incognito controls block or pause Pattern C
    - cancellation command cancels active children
    - background work appears in orchestrator status and cost governance

OBL-BDSM-NEW-SUBAGENT-UTILITY-LEDGER-01:
  target_spec: BDSM v6.5+
  target_section: utility ledgers / specialist reputation
  source: V5.2 §2.9 + §3.4
  requirement_type: REQ
  status: MISSING
  acceptance:
    - quality and reliability α/β separated
    - composite version key includes identity and capability version
    - deferred advice lifecycle implemented
    - learning suppression fields honored

OBL-BDSM-LEARNING-VIS-SCOPE-CANONICAL-01:
  target_spec: BDSM v6.5+ OR DOC73 V1.5.1 (V1.6 queued for next minor)
  target_section: LearningVisibilityScope canonical schema
  source: V5.2 §3.4
  requirement_type: REQ
  status: PARTIAL
  acceptance:
    - canonical LearningVisibilityScope schema published
    - V5.2 passthrough stub replaced by canonical import

OBL-BDSM-UNIFIED-SPECIALIST-LEDGER-01:
  target_spec: BDSM v6.5+
  target_section: specialist reputation unification
  source: V5.2 §3.4 + DOC23 §A7
  requirement_type: REQ
  status: MISSING
  acceptance:
    - V5.2 Elnor dispatches and DOC23 task-module traces contribute to unified read
    - per-path weights specified
    - slot rendering consumes unified read after v6.5+

OBL-D23B-SLOT-REGISTRATION-MECHANICS-01:
  target_spec: DOC23 Addenda B Core R0.8
  target_section: §3D slot registration extension
  source: V5.2 §1.4C + §3.2
  requirement_type: REQ
  status: MISSING
  acceptance:
    - DOC23 task-awareness and task-opportunity slots registered with DOC24 InjectionSlotRegistry shape
    - ambient-card placement decision made normatively
    - top-k template/preset/directive slots specify rendering constraints and packet-kind requirements
```

Update `OBL-EC-NEW-AGENT-CONFIG-SUGGESTED-USE-CASES-01` so it blocks no DOC72 node-kind row. The EC suggested-use-cases field and DOC72 node-kind work are parallel; neither blocks the other.

---

## §5 Configuration tuning guidance

### §5.1 Spawn and concurrency defaults

Default:

```json
{
  "maxSpawnDepth": 3,
  "maxChildrenPerAgent": 5,
  "maxConcurrent": 8,
  "runTimeoutSeconds": 900,
  "archiveAfterMinutes": 60,
  "maxSpawnsPerMinute": 30,
  "maxSpawnsPerHour": 200
}
```

`maxConcurrent` is global across all ELNOR agents and active task-module specialist dispatches, not per-agent.

| Usage profile | Recommended `maxConcurrent` |
|---|---:|
| Chat only, mostly inline work | 8 |
| Chat with occasional 5-wide research | 12 |
| Chat with 1–2 saved tasks running in background | 16 |
| Active multi-task work, 3+ saved tasks plus chat | 24 |
| Heavy continuous-eval load plus active multi-task | 32 |

When the global cap is reached, Q dashboard shows spawn-pressure indicator listing active competitors. User can wait, raise cap, or cancel background jobs.

### §5.2 Failure lockouts

After three consecutive `invocation_error` outcomes with same `error_kind`, set `failure_lockout_until = now + 20m`. Manual override requires EC Core admin command. Failure lockout affects `availability_state`, not durable `lifecycle_state`.

### §5.3 Rate guardrails

Per-parent-session defaults:

```text
maxSpawnsPerMinute = 30
maxSpawnsPerHour = 200
```

Rate-limit denial emits a terminal receipt with `terminal_status = "denied_admission"` and retry-after metadata.

---

## Appendix A — Accepted / deferred / rejected adjudication coverage

### A.1 Accepted and folded into V5.2

- ADJ-001 prompt rewrite and budget.
- ADJ-002 dispatch envelope.
- ADJ-003 dispatch receipt.
- ADJ-004 EC/PropA exposure gate and admission order.
- ADJ-005 effective tool/auth grant.
- ADJ-006 system specialist incorporation rule.
- ADJ-007 Pattern G verifier.
- ADJ-008 Pattern H orchestration-plan dry-run.
- ADJ-009 room lifecycle.
- ADJ-010 EC background work registry.
- ADJ-011 spawn-tree circuit breaker.
- ADJ-012 output taint.
- ADJ-013 DOC23 §A7 boundary and trace schema.
- ADJ-014 incognito / processing / learning suppression.
- ADJ-015 name resolution.
- ADJ-016 ranking.
- ADJ-017 slot rendering.
- ADJ-018 retry/fallback.
- ADJ-019 DOC23 coexistence.
- ADJ-020 Task Agent pre-registration.
- ADJ-021 capability schema extensions.
- ADJ-022 OP-A rows and cycle fix.
- ADJ-023 reference corrections.
- ADJ-024 `allowed_coordination_points` preserved.
- ADJ-025 DOC72/EC source-of-truth split.
- ADJ-026 BDSM weight discipline.
- ADJ-027 pre-registered lifecycle.
- ADJ-028 availability state.
- ADJ-029 scope vocabulary.
- ADJ-030 contribution trace.
- ADJ-031 DOC12 room vs DOC23 forum/board boundary.
- ADJ-032 folded into ADJ-023 reference corrections.
- ADJ-033 cancellation and partial results.
- ADJ-034 folded into ADJ-026 partial-accept weight discipline.
- ADJ-035 capability version in utility key.
- ADJ-036 folded into ADJ-004 effective runtime gating.
- ADJ-037 learning visibility scope structural stub.
- ADJ-038 fork fallback visibility.
- ADJ-039 timeout/archive clarity.
- ADJ-040 maxSpawnDepth vs DOC23 max_call_depth.
- ADJ-041 zero-specialist caveat.
- ADJ-042 single-user specialist rationale.
- ADJ-043 DOC73 reference clarity.
- ADJ-044 friendly-name match lint.
- ADJ-045 DOC24 slot registration conformance.
- ADJ-046 Pattern E coordination protocol.
- ADJ-047 global concurrency guidance.
- ADJ-048 BDSM forward-compat.
- ADJ-049 DOC23 compatibility mapping.
- ADJ-050 caching discipline.
- ADJ-051 DOC24 implementation dependency note.
- ADJ-052 DOC23 slot-registration mechanics obligation.
- ADD-B optional provider advisor capability.
- ADD-E cost-aware dynamic routing partial.
- ADD-F DOC12 room blackboard.
- ADD-I typed-entrypoint capabilities.

### A.2 Deferred

- ADD-A continuous specialist evaluation / background red-teaming.
- ADD-C marketplace/shareable specialist profiles.
- ADD-D semantic task caching.
- ADD-G DSPy self-refining specialists.
- ADD-H external A2A/MCP agent gateway.
- Long-running resumable specialist as separate future pattern if non-DOC23 use case emerges.
- DEF-1 cold-start reputation aggressiveness tuning.
- DEF-2 finer deferred-advice subcategories.

### A.3 Rejected

- Claim that V5.1 allowed sub-agents to take over final user-facing output.
- Standalone cross-agent scratchpad separate from DOC12 room blackboard.
- Prompt-level “TASK SEAM” line in §1.4; task surfacing remains DOC24/DOC23-owned.

---

## Appendix B — Open implementation notes

1. DOC24 R3.2+ must implement OP-A V3.15 DOC23 TaskModeDecision / TaskOpportunityPacket rows before natural task-chip surfacing fully works.
2. DOC23 Addenda B Core R0.8 should resolve ambient task-awareness card placement and slot registration mechanics.
3. BDSM v6.5+ should publish canonical LearningVisibilityScope and unified specialist reputation read.
4. DOC25 must map DocumentIntelligenceAgent logical tools before that agent is invokable.
5. EC Core must own all durable writes and command closure for Agents page and chat-driven agent creation.
6. Provider caching and advisor integration are adapter metadata, not provider-specific core architecture.

---

## End of Operative Spec V5.2