From 6696c06e4f0f3291f5cf79bde0837e518c8760ff Mon Sep 17 00:00:00 2001 From: dullfig Date: Wed, 7 Jan 2026 12:31:47 -0800 Subject: [PATCH] fixing docs --- docs/configuration.md | 2 +- docs/listener-class-v2.1.md | 213 ++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 docs/listener-class-v2.1.md 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