diff --git a/docs/configuration.md b/docs/configuration.md
index dbfcd4e..eb11239 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,4 +1,4 @@
-G# Configuration — organism.yaml (v2.0)
+G# Configuration — organism.yaml (v2.1)
The entire organism is declared in a single YAML file (default: `config/organism.yaml`).
Loaded at bootstrap — single source of truth for initial composition.
diff --git a/docs/listener-class-v2.1.md b/docs/listener-class-v2.1.md
new file mode 100644
index 0000000..5ec23bf
--- /dev/null
+++ b/docs/listener-class-v2.1.md
@@ -0,0 +1,213 @@
+**listener-class-v2.1.md**
+**January 07, 2026**
+**AgentServer v2.1 — The Listener Class & Registration**
+
+This is the canonical documentation for defining and registering capabilities in AgentServer v2.1.
+All other descriptions of listener creation are superseded by this file.
+
+### Purpose
+A **Listener** declares a bounded, sovereign capability consisting of:
+- A precise input contract (an `@xmlify` dataclass)
+- A pure async handler function
+- A mandatory human-readable description
+
+From this declaration alone, the organism autonomously generates:
+- Cached XSD schema
+- Example payload
+- Rich tool-prompt fragment
+- Dedicated preprocessing pipeline
+- Routing table entry with a fully derived, globally unique root tag
+
+The developer supplies **only** Python code and a minimal YAML entry. Everything else is drift-proof and attack-resistant.
+
+### Root Tag Derivation (Locked Rule)
+The wire-format root element of every payload is constructed automatically as:
+
+```
+{lowercase_registered_name}.{lowercase_dataclass_name}
+```
+
+Examples:
+
+| Registered name | Dataclass name | → Root tag on wire |
+|-----------------------|---------------------|------------------------------------------|
+| calculator.add | AddPayload | calculator.add.addpayload |
+| calculator.multiply | MultiplyPayload | calculator.multiply.multiplypayload |
+| researcher | ResearchPayload | researcher.researchpayload |
+| web_search | SearchPayload | web_search.searchpayload |
+
+This derivation happens once at registration time.
+The developer **never** writes, repeats, or overrides the root tag.
+
+### YAML Entry (organism.yaml)
+Required fields:
+
+```yaml
+listeners:
+ - name: calculator.add # Unique across organism, dots allowed for hierarchy
+ payload_class: tools.calculator.AddPayload
+ handler: tools.calculator.add_handler
+ description: "Adds two integers and returns their sum." # Mandatory for prompt generation
+
+ - name: researcher
+ payload_class: agents.researcher.ResearchPayload
+ handler: agents.researcher.research_handler
+ description: "Primary research agent that reasons and coordinates tools."
+ agent: true # Flags LLM agent → unique root enforced, own_name exposed
+ peers: # Allowed targets this agent may address
+ - calculator.add
+ - web_search
+
+ - name: search.google
+ payload_class: gateways.google.SearchPayload
+ handler: gateways.google.search_handler
+ description: "Google search gateway."
+ broadcast: true # Opt-in: permits sharing root tag with other search.* listeners
+```
+
+Optional flags:
+- `agent: true` → designates an LLM-driven listener (enforces unique root tag, exposes `own_name` in metadata)
+- `peers:` → list of registered names this listener is allowed to call (enforced by pump for agents)
+- `broadcast: true` → allows multiple listeners to intentionally share the same derived root tag (used for parallel gateways/retrievers)
+
+### Python Declaration
+```python
+from xmlable import xmlify
+from dataclasses import dataclass
+from xml_pipeline import Listener, HandlerMetadata
+
+@xmlify
+@dataclass
+class AddPayload:
+ """Addition request."""
+ a: int = 0 # Field docstrings become parameter descriptions in prompts
+ b: int = 0
+
+async def add_handler(
+ payload: AddPayload,
+ metadata: HandlerMetadata
+) -> bytes:
+ result = payload.a + payload.b
+ return f"{result}".encode("utf-8")
+```
+
+### Handler Signature (Locked)
+```python
+async def handler(
+ payload: PayloadDataclass, # Deserialized, XSD-validated instance
+ metadata: HandlerMetadata # Small, trustworthy context
+) -> bytes:
+ ...
+```
+
+### HandlerMetadata (frozen, read-only)
+```python
+@dataclass(frozen=True)
+class HandlerMetadata:
+ thread_id: str # Opaque UUID matching in envelope
+ from_id: str # Registered name of the sender (pump-injected, trustworthy)
+ own_name: str | None = None # Populated ONLY for listeners with agent: true
+ is_self_call: bool = False # Convenience flag: from_id == own_name
+```
+
+Typical uses:
+- Stateful tools → key persistent data by `thread_id`
+- Agents → reason about provenance using `from_id`, optionally refer to themselves via `own_name`
+
+### Handler Return Requirements
+The handler **must** return a `bytes` object containing one or more payload root elements.
+
+Returning `None` or a non-`bytes` value is a programming error.
+
+The message pump protects the organism by injecting a diagnostic payload if invalid bytes are returned:
+
+```python
+if response_bytes is None or not isinstance(response_bytes, bytes):
+ response_bytes = b"Handler failed to return valid bytes — likely missing return statement or wrong type"
+```
+
+This ensures:
+- The thread never hangs due to a forgotten return
+- The error is immediately visible in logs and thread history
+- LLM agents can observe and self-correct
+
+**Correct examples**
+```python
+async def good(... ) -> bytes:
+ return b"42"
+
+async def also_good(... ) -> bytes:
+ # fast synchronous-style computation
+ return b""
+```
+
+**Dangerous (triggers injection)**
+```python
+async def bad(... ):
+ computation()
+ # forgot return → implicit None
+```
+
+Always explicitly return `bytes`.
+
+### Multi-Payload Emission & Envelope Injection
+Handlers return **raw XML fragments only**. The pump performs all enveloping:
+
+1. Wrap response in `` (tolerant of dirty output)
+2. Extract all root elements found inside
+3. For each extracted payload:
+ - Inherit current ``
+ - Inject `` = registered name of the executing listener
+ - Build full `` envelope
+ - Re-inject into the pipeline(s) matching the payload’s derived root tag
+
+Example emission enabling parallel tool calls + self-continuation:
+
+```python
+async def researcher_step(... ) -> bytes:
+ return b"""
+ Need weather and a calculation...
+
+ current weather Los Angeles
+
+
+ 7
+ 35
+
+ """
+```
+
+The three payloads are automatically enveloped with correct provenance and routed.
+
+### Registration Lifecycle
+At startup or privileged OOB hot-reload:
+
+1. Import `payload_class` and `handler`
+2. Derive root tag (`registered_name.dataclass_name`)
+3. Enforce global uniqueness (unless `broadcast: true`)
+4. Validate mandatory description
+5. Generate and cache XSD, example, and prompt fragment
+6. Instantiate dedicated preprocessing pipeline
+7. Insert into routing table
+
+Any failure (duplicate root, missing description, import error) → clear error message and abort/reject.
+
+### Best Practices
+- Use hierarchical registered names (`category.sub.action`) for logical grouping and readable wire tags.
+- Choose clear, specific dataclass names — they become permanent parts of the wire format.
+- Always write a concise, accurate description — it leads every auto-generated tool prompt.
+- For agents, declare only the minimal necessary `peers` — keeps prompts bounded and secure.
+- Pure tools rarely need a `peers` entry.
+
+### Summary of Key Invariants
+- Root tag fully derived, never manually specified
+- Global uniqueness guaranteed by registered-name prefix
+- Handlers remain pure except for small trustworthy metadata
+- All envelope construction and provenance injection performed exclusively by the pump
+- Zero manual XSDs, examples, or prompt fragments required
+
+This specification is now locked for AgentServer v2.1. All code, examples, and future documentation must align with this file.
+
+---
+
+Ready for the next piece (message pump implementation notes, OOB channel, stateful listener examples, etc.) whenever you are.
\ No newline at end of file