Skip to content

Commit c1b0436

Browse files
committed
docs: ADR-017 pod defaults, service self-description, and compilation principles
Add ADR-017 and implementation plan for three-phase improvement: - Phase 1: pod-level defaults with replace-on-declare + spread syntax - Phase 2: claw.describe service descriptors and provider-owned feeds - Phase 3: unified CLAWDAPUS.md context document Add compilation principles to AGENTS.md, README.md, and MANIFESTO.md establishing compile-time wiring, provider-owns-consumer-subscribes, and service self-description as core architectural invariants.
1 parent 22253ef commit c1b0436

File tree

5 files changed

+744
-9
lines changed

5 files changed

+744
-9
lines changed

AGENTS.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ Core docs:
1414
- `docs/decisions/` — ADRs
1515
- `docs/plans/` — implementation plans and historical design notes
1616

17+
## Compilation Principles
18+
19+
`claw up` is a compiler. These principles govern the pipeline and must not be violated by new features:
20+
21+
1. **Compile-time, not runtime.** All wiring — feeds, skills, identity, surfaces — is resolved during `claw up`. No runtime self-registration. The generated compose file is the single source of truth.
22+
2. **Provider-owns, consumer-subscribes.** Services declare what they offer (feeds, endpoints, auth). Agents subscribe by name. Consumers never need to know a service's URL path or TTL.
23+
3. **Pod-level defaults, service-level overrides.** Shared config is declared once at pod level. Services inherit by default, override or extend (`...` spread) as needed.
24+
4. **One canonical descriptor.** A service's capabilities are declared once (via `claw.describe` in the image) and projected into whatever artifacts need them. No manual duplication across pod YAML, skills, and CLAWDAPUS.md.
25+
5. **Services self-describe.** Images carry structured descriptors. `claw up` extracts and compiles them. Framework adapters (RailsTrail, etc.) generate descriptors from code introspection.
26+
27+
See ADR-017 for the full design and `docs/plans/2026-03-22-pod-defaults-and-service-self-description.md` for implementation details.
28+
1729
## Trust Order
1830

1931
There is some doc drift in the repo. When sources disagree, trust them in this order:
@@ -110,6 +122,9 @@ Do not assume older docs mentioning only a subset are current.
110122
- OpenClaw config and cron paths are mounted as directories, not single files, because the runtime performs atomic rewrites.
111123
- OpenClaw `openclaw health --json` can emit noise to stderr. The repo handles it as a stdout-first parse path.
112124
- cllama logger (`cllama/internal/logging/logger.go`): field `intervention *string` has no `omitempty` — every event emits `"intervention": null`. Emitted `type` values are `request`, `response`, `error`, `intervention`. No `drift_score` exists in the reference implementation. The spec (`CLLAMA_SPEC.md` §5) omits `error` from its type enum and uses `intervention_reason` where the logger uses `intervention`.
125+
- Hermes SOUL.md identity: The Hermes runner seeds a default SOUL.md ("You are Hermes, made by Nous Research") on first boot via `hermes_cli/default_soul.py`. The Clawdapus Hermes driver writes its own `SOUL.md` to `hermes-home/` during `Materialize` to override this with the agent's contracted identity. Persona SOUL.md takes priority when configured.
126+
- Hermes `.env` passthrough: Container env vars from compose `environment:` are NOT available in Hermes agent tool execution. Only vars in `allowedEnvPassthroughKeys()` (`internal/driver/hermes/config.go`) reach the tool runtime via the `.env` file. New env vars that agents need (e.g. `CLAW_API_TOKEN`) must be added to this list.
127+
- Pod-level `x-claw` only accepts `pod`, `master`, and `handles-defaults`. Provider keys (`cllama-env`) must be service-level — use YAML anchors to stay DRY. See `examples/trading-desk/claw-pod.yml` for the pattern.
113128

114129
## Current Behavior Worth Knowing
115130

MANIFESTO.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ An agent is useless if it doesn't know what it can do or who it is. Clawdapus so
129129
Every Claw receives a `CLAWDAPUS.md` file injected into its workspace. This is the infrastructure layer's letter to the agent. It lists the agent's identity, its allowed surfaces, and an index of available skill files.
130130

131131
**Skills Discovery:**
132-
When an agent declares a `service://` surface, Clawdapus queries that service to find out what it does. Services self-describe via MCP tool listings, OpenAPI specs, or an explicit `claw.skill.emit` label. Clawdapus generates a markdown "skill" file explaining how to use the service and mounts it into the agent's skill directory. Add a service to the pod, and the agent automatically receives the manual on how to use it.
132+
When an agent declares a `service://` surface, Clawdapus queries that service to find out what it does. Services self-describe via a structured `claw.describe` descriptor in their image — advertising feeds, endpoints, auth requirements, and a skill file. Framework adapters like RailsTrail generate descriptors from code introspection (routes, state machines, manual actions). `claw up` extracts these at compile time and projects them into CLAWDAPUS.md, feed manifests, and the effective agent contract. Add a service to the pod, and the agent automatically receives the manual on how to use it.
133133

134134
**Social Topology (`HANDLE`):**
135135
Agents have identities on chat platforms. The `HANDLE` directive declares a bot's platform identity (e.g., `HANDLE discord`). Clawdapus translates this into the runner's native configuration (enabling the Discord plugin). Crucially, it also broadcasts the agent's Handle ID as an environment variable to *every* service in the pod. This enables the "Leviathan Pattern": a non-AI API service can read `CLAW_HANDLE_CRYPTO_CRUSHER_DISCORD_ID` and dynamically construct an `@mention` to alert a specific bot in a chat channel without hardcoding IDs.
@@ -178,6 +178,7 @@ Architecture decisions and implementation plans live alongside this manifesto:
178178
- [ADR-002: Runtime Authority](docs/decisions/002-runtime-authority.md) — compose-only lifecycle, SDK read-only
179179
- [ADR-003: Topology Simplification](docs/decisions/003-topology-simplification.md) — moving channel identity to HANDLE
180180
- [ADR-004: Service Surface Skills](docs/decisions/004-service-surface-skills.md)`claw.skill.emit` and fallback generation
181+
- [ADR-017: Pod-Level Defaults and Service Self-Description](docs/decisions/017-pod-defaults-and-service-self-description.md)`claw.describe`, provider-owned feeds, compilation principles
181182
- [ADR-006: INVOKE Scheduling](docs/decisions/006-invoke-scheduling.md) — native runner scheduling over system cron
182183
- [ADR-007: Credential Starvation](docs/decisions/007-llm-isolation-credential-starvation.md) — isolating LLM traffic without breaking egress
183184
- [ADR-008: cllama Sidecar Standard](docs/decisions/008-cllama-sidecar-standard.md) — formalizing the context-aware proxy

README.md

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,24 +150,41 @@ SKILL policy/risk-limits.md # operator policy — mounted read-only into r
150150
```yaml
151151
x-claw:
152152
pod: trading-desk
153+
master: octopus
154+
cllama-defaults:
155+
proxy: [passthrough]
156+
env:
157+
OPENROUTER_API_KEY: "${OPENROUTER_API_KEY}"
158+
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY}"
159+
surfaces-defaults:
160+
- "service://trading-api"
161+
- "volume://shared-research read-write"
162+
feeds-defaults: [market-context] # resolved from trading-api's claw.describe
153163
services:
154164
tiverton:
155165
image: trading-desk-tiverton:latest
156166
build:
157167
context: ./agents/tiverton
158168
x-claw:
159169
agent: ./agents/tiverton/AGENTS.md
160-
cllama: passthrough
161-
cllama-env:
162-
OPENROUTER_API_KEY: "${OPENROUTER_API_KEY}"
163-
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY}"
164170
handles:
165171
discord:
166172
id: "${TIVERTON_DISCORD_ID}"
167173
username: "tiverton"
168-
surfaces:
169-
- "service://trading-api"
170-
- "volume://shared-research read-write"
174+
invoke:
175+
- schedule: "15 8 * * 1-5"
176+
name: "Pre-market synthesis"
177+
message: "Run pre-market synthesis and post the floor briefing."
178+
to: trading-floor
179+
```
180+
181+
Services inherit `cllama-defaults`, `surfaces-defaults`, and `feeds-defaults` from the pod. Override any field to replace; use `...` spread to extend:
182+
183+
```yaml
184+
x-claw:
185+
skills:
186+
- ... # inherit pod defaults
187+
- ./policy/escalation.md # add coordinator-only skill
171188
```
172189

173190
`claw build` transpiles the Clawfile to a standard Dockerfile. `claw up` parses the pod YAML, runs driver enforcement, generates per-agent configs, wires the cllama proxy, and calls `docker compose`. The output is standard OCI images and a standard compose file. Eject from Clawdapus anytime — you still have working Docker artifacts.
@@ -331,9 +348,25 @@ When many services share the same Discord guild/channel topology, put that share
331348

332349
---
333350

351+
## Compilation Principles
352+
353+
`claw up` is a compiler. It reads the pod file, inspects images, and emits deterministic runtime artifacts. These principles govern the compilation pipeline:
354+
355+
1. **Compile-time, not runtime.** All wiring — feeds, skills, identity, surfaces — is resolved during `claw up`. No runtime self-registration. The generated compose file is the single source of truth for what's deployed.
356+
357+
2. **Provider-owns, consumer-subscribes.** Services declare what they offer (feeds, endpoints, auth). Agents subscribe by name. The consumer should never need to know a service's URL path or TTL — that's the provider's concern.
358+
359+
3. **Pod-level defaults, service-level overrides.** Anything shared across most services — proxy config, surfaces, feeds, skills — is declared once at pod level. Services inherit by default and override or extend as needed.
360+
361+
4. **One canonical descriptor.** A service's capabilities, feeds, and endpoints are declared once (via `claw.describe` in the image) and projected into whatever artifacts need them — CLAWDAPUS.md, feed manifests, effective agent contracts.
362+
363+
5. **Services self-describe.** Images can carry a structured descriptor (`LABEL claw.describe=...`) that advertises feeds provided, auth requirements, and a skill file. `claw up` extracts and compiles these into the pod. Framework-specific adapters (e.g., RailsTrail for Rails apps) can generate descriptors from code introspection.
364+
365+
---
366+
334367
## Surfaces, Skills, and CLAWDAPUS.md
335368

336-
Every Claw receives a generated `CLAWDAPUS.md` — always in context listing its surfaces, mount paths, and available skills. Services self-describe via MCP listings, OpenAPI specs, or `LABEL claw.skill.emit=/path/to/SKILL.md` in their image. Custom service-emitted manuals are also compiled into the effective agent contract automatically, so workflow-critical API docs are available in prompt context without extra pod YAML. Add a service, the skill map updates. No code changes.
369+
Every Claw receives a generated `CLAWDAPUS.md` — the single context document listing surfaces, mount paths, peer handles, feeds, and available skills. Service descriptions from `claw.describe` labels or `claw.skill.emit` are inlined directly into CLAWDAPUS.md surface sections, so workflow-critical API docs are always in prompt context without extra pod YAML. Add a service, the skill map updates. No code changes.
337370

338371
```bash
339372
$ claw skillmap crypto-crusher-0
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# ADR-017: Pod-Level Defaults and Service Self-Description
2+
3+
**Date:** 2026-03-22
4+
**Status:** Accepted
5+
**Depends on:** ADR-004 (Service Surface Skills), ADR-013 (Context Feeds)
6+
**Implementation:** docs/plans/2026-03-22-pod-defaults-and-service-self-description.md
7+
8+
## Context
9+
10+
Clawdapus treats agents as untrusted workloads governed by operator-authored pod contracts. The pod file (`claw-pod.yml`) is the deployment source of truth — inspectable, diffable, deterministic. `claw up` compiles it into runtime artifacts.
11+
12+
Two problems have emerged as pods grow:
13+
14+
1. **Operator repetition.** Every claw in a pod repeats the same `cllama`, `cllama-env`, `surfaces`, `feeds`, and `skills` blocks even when they're identical. Tiverton-house (5 claws, 1 infra service, 1 governor) has four services sharing identical cllama, feeds, and surfaces stanzas. YAML anchors mitigate visual noise but not structural duplication.
15+
16+
2. **Service knowledge is in the wrong place.** Feeds are declared by consumers, not providers. A claw that wants market data must know the trading API's endpoint path, TTL, and auth scheme. Service skills are either a single extracted markdown file (`claw.skill.emit`) or a generic hostname+ports stub. Services cannot advertise their capabilities in a structured way that the pod compiler can consume.
17+
18+
Both problems share a root cause: the pod surface lacks inheritance and the compilation pipeline lacks a service descriptor contract.
19+
20+
## Decision
21+
22+
### 1. Pod-Level Defaults
23+
24+
Pod-level `x-claw` gains four new default fields alongside the existing `handles-defaults`:
25+
26+
- `cllama-defaults` — proxy type and provider env keys
27+
- `surfaces-defaults` — surface list
28+
- `feeds-defaults` — feed list
29+
- `skills-defaults` — skill file list
30+
31+
Every claw-managed service inherits these unless it declares its own value for that field.
32+
33+
### 2. Replace-on-Declare with Spread
34+
35+
Override semantics follow one rule: **if a service declares a list field, it replaces the defaults entirely.**
36+
37+
To extend defaults rather than replace them, the service uses a `...` spread token in the list:
38+
39+
```yaml
40+
skills:
41+
- ... # defaults expand here
42+
- ./policy/escalation.md # then this is appended
43+
```
44+
45+
- No `...` → full replacement
46+
- `...` present → defaults splice at that position
47+
- At most one `...` per list
48+
49+
This is more expressive than any standard YAML merge convention while remaining unambiguous. `cllama-defaults.env` is a map and merges additively (service keys win on collision), matching the existing `handles-defaults` pattern.
50+
51+
### 3. Service Self-Description (`claw.describe`)
52+
53+
Services declare a structured JSON descriptor via image label:
54+
55+
```dockerfile
56+
LABEL claw.describe=/app/.claw-describe.json
57+
```
58+
59+
The descriptor advertises feeds provided, auth requirements, a human-readable description, and an optional skill file path. `claw up` extracts it from the image (same mechanism as `claw.skill.emit`) and compiles it into the pod manifest.
60+
61+
The descriptor does not contain a service name — deployment identity comes from the pod YAML, not the image. One image can back multiple services.
62+
63+
### 4. Provider-Owned Feeds with Consumer Subscription
64+
65+
Feeds move from consumer-declared to provider-declared. A service's descriptor advertises its feeds. Consumers subscribe by name:
66+
67+
```yaml
68+
feeds: [market-context]
69+
```
70+
71+
`claw up` resolves the name against a feed registry built from service descriptors. Explicit feed declarations (source + path + ttl) bypass the registry and work as before.
72+
73+
Resolution happens in `claw up` after image inspection, not in the parser. The parser stores unresolved feed names; `claw up` resolves them once the registry exists.
74+
75+
### 5. Unified Context Document
76+
77+
Generated surface and handle skill files are collapsed into CLAWDAPUS.md. One generated context document per agent instead of N files. Service descriptions retain their contractual weight — they're still injected into `AGENTS.generated.md` as guide content, just sourced from CLAWDAPUS.md sections rather than separate files.
78+
79+
Operator-authored skills (policy files, includes with `mode: reference`) remain as separate mounted files.
80+
81+
### 6. Compile-Time Only
82+
83+
All registration and description happens during `claw up`. No runtime self-registration endpoints. The generated compose file and runtime artifacts remain the single source of truth for what's deployed. This preserves the inspectable, diffable deployment model that is Clawdapus's core value proposition.
84+
85+
## Rationale
86+
87+
**Why not runtime self-registration?** Clawdapus's value is deterministic, auditable deployment. If services register at boot, the running state diverges from the pod file. The right version is image self-description compiled by `claw up`.
88+
89+
**Why replace-on-declare instead of always-merge?** List merging is inherently ambiguous (append? prepend? deduplicate by what?). Every system that attempts it (Helm, Kustomize, Ansible) ends up with surprising edge cases. Replace is the simplest default. The `...` spread provides controlled extension when needed.
90+
91+
**Why no `name` in the descriptor?** A single image can back multiple compose services. Tiverton-house uses one hermes base image for all traders. Binding the descriptor to a service name would break image reuse.
92+
93+
**Why two-phase feed resolution?** The parser has no image knowledge. Descriptors are extracted from images during `claw up`. Trying to resolve feed names in the parser would require passing image inspection state into the YAML parser, coupling two independent phases.
94+
95+
## Consequences
96+
97+
**Positive:**
98+
- Pod files shrink dramatically. Tiverton-house's per-service `x-claw` blocks reduce to identity + overrides only.
99+
- Services self-describe their feeds, auth, and capabilities. Consumers subscribe by name.
100+
- One generated context document per agent instead of N redundant files.
101+
- The `...` spread convention is simple and more expressive than any standard YAML merge.
102+
- RailsTrail (and future framework adapters) can generate descriptors from introspection, closing the loop between app code and pod contracts.
103+
104+
**Negative:**
105+
- Breaking change to pod YAML surface. Tiverton-house pod must be rewritten. (Acceptable — it's the only production pod.)
106+
- Two-phase feed resolution adds a step to `claw up`. Unresolved feeds are a new error category.
107+
- `claw.skill.emit` becomes redundant once `claw.describe` with `skill` field is live. Deprecation timeline TBD.
108+
- The `...` spread is a custom convention. Operators must learn it. (Mitigated by simplicity — one token, one rule.)

0 commit comments

Comments
 (0)