This is the canonical repo-level agent guide for Clawdapus. CLAUDE.md should be a symlink to this file.
Clawdapus is infrastructure-layer governance for AI agent containers. The claw CLI is a Go binary that treats agents as untrusted workloads: reproducible, inspectable, diffable, and killable.
Core docs:
MANIFESTO.md— project visionREADME.md— current user-facing CLI and examplesdocs/CLLAMA_SPEC.md— cllama proxy contractdocs/decisions/— ADRsdocs/plans/— implementation plans and historical design notes- GitHub Project Board — prioritized roadmap (kanban).
Workflow:
- move an issue to
In progresswhen actively working on it - use
Readyonly for work that is queued up but not yet being worked - move an issue to
In reviewwhen implementation, docs, and verification are complete - move an issue to
Doneonly after review/acceptance, not immediately after coding
- move an issue to
claw up is a compiler. These principles govern the pipeline and must not be violated by new features:
- 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. - 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.
- Pod-level defaults, service-level overrides. Shared config is declared once at pod level. Services inherit by default, override or extend (
...spread) as needed. - One canonical descriptor. A service's capabilities are declared once (via
claw.describein the image) and projected into whatever artifacts need them. No manual duplication across pod YAML, skills, and CLAWDAPUS.md. - Services self-describe. Images carry structured descriptors.
claw upextracts and compiles them. Framework adapters (RailsTrail, etc.) generate descriptors from code introspection.
See ADR-017 for the full design and docs/plans/2026-03-22-pod-defaults-and-service-self-description.md for implementation details.
There is some doc drift in the repo. When sources disagree, trust them in this order:
- Current code in
cmd/claw/andinternal/ - Current tests
- Examples under
examples/ - ADRs in
docs/decisions/ - Plans/reviews in
docs/plans/anddocs/reviews/
Example: TESTING.md still talks about e2e, but the build tags currently in-tree are integration and spike.
Current top-level commands are:
claw buildclaw upclaw downclaw psclaw logsclaw healthclaw inspectclaw doctorclaw initclaw agent addclaw compose(use this liberally instead of invoking docker directly)
Useful current behavior:
claw upwritescompose.generated.ymlnext to the pod file.- If the pod contains managed
x-clawservices,claw upcurrently requires detached mode: useclaw up -d. docker composeis the sole lifecycle writer. Docker SDK usage is read-only.
If you are debugging or changing behavior, these are the main entry points:
cmd/claw/compose_up.go— main runtime orchestration pathinternal/pod/—claw-pod.ymlparsing and compose emissioninternal/clawfile/— Clawfile parsing and Dockerfile emissioninternal/driver/— driver registry and per-runner implementationsinternal/cllama/— cllama context generation and wiring helpersinternal/inspect/— claw label parsing from imagesinternal/describe/—claw.describeservice descriptor extraction, parsing, and feed registryinternal/persona/— persona materializationcllama/— proxy implementation source
The best end-to-end fixtures are:
examples/quickstart/examples/trading-desk/examples/rollcall/(this must remain a full spike test)
Driver directories currently in-tree:
internal/driver/openclawinternal/driver/hermesinternal/driver/nanobotinternal/driver/nanoclawinternal/driver/picoclawinternal/driver/microclawinternal/driver/nullclawinternal/driver/shared
Do not assume older docs mentioning only a subset are current.
- A
Clawfileis parsed and emitted into a standard Dockerfile using image labels for Clawdapus directives. - A
claw-pod.ymlis parsed from service-levelx-clawblocks. Current parsed fields includeagent,persona,cllama,cllama-env,count,handles,include,surfaces,skills, andinvoke. Pod-levelx-clawalso acceptssequential-conformance: true. count > 1expands into ordinal-named compose services likesvc-0,svc-1, etc.x-claw.masteris parsed inrawPodClawbut NOT propagated intoPod— the field is silently dropped. Any feature depending on it needs the parser→Pod→compose_up chain wired first.- cllama wiring is resolved before materialization in a two-pass
claw upflow. - Generated runtime artifacts like
AGENTS.generated.md,CLAWDAPUS.md, cllama context files, and runner configs are produced under runtime dirs duringclaw up. - cllama context layout: host-side at
.claw-runtime/context/<agent-id>/containingAGENTS.md,CLAWDAPUS.md,metadata.json. Mounted into cllama container at/claw/context/<agent-id>/. Thecontext/directory segment is required.
- Bug fixes in one driver often apply to all 7. When fixing driver behavior (permissions, config defaults, env vars), check all drivers in
internal/driver/*/driver.goandconfig.go— not just the one mentioned in the issue. - Runtime directories created by
Materialize()use0o777(not0o700) so container users with different uids can write. Do not regress this. - All drivers set
mention_only(or equivalent likerequireMention,DISCORD_REQUIRE_MENTION) for Discord channels. Without this, multi-agent pods enter feedback loops. - All drivers explicitly set
HOMEin the container env map to match their config mount path. Container base images may run as root or a different user than expected. cllama/is a git submodule pointing to a public SSH repo. Freshgit cloneleaves it empty. Infra images (cllama, clawdash) are published to ghcr.io as public packages to avoid this for end users.cllama/has its own.git— changes require two commits: one insidecllama/(for feeds/proxy code), thengit add cllama && git commitin the repo root to update the pointer. Shell working directory can silently drift tocllama/between commands — use absolute paths for git operations or verify withpwdfirst.internal/feeds/and other cllama internals live atcllama/internal/, not at the repo root.ensureImage()incompose_up.gohas a 3-step fallback: local image →docker pull→ local Dockerfile build → git URL build. All steps must be considered when debugging image failures.- Managed services require
claw up -dbecause post-apply verification is fail-closed. - Multi-proxy cllama is represented in the data model but runtime currently fails fast if more than one proxy type is declared.
- cllama proxy handler (
cllama/internal/proxy/handler.go) is pure passthrough: it rewrites themodelfield and forwards. It does NOT touch themessagesarray. No prompt decoration, no system message injection, no middleware hooks exist. Two distinct code paths handle OpenAI format (messages[]) vs Anthropic format (top-levelsystemfield). - cllama providers.json: v1 format uses top-level
api_key; v2 usesversion: 2+keys[]array withstate/secret/id. Old cllama images (pre-v0.2.2) silently load v2 files with empty key pools → every request returns "missing API key for {provider}" 502. Afterdocker pull ghcr.io/mostlydev/cllama:latest, usedocker compose -f compose.generated.yml up -d cllama(notrestart) to recreate the container with the new image. - cllama SSE endpoint for debugging provider key state:
curl -N -H "Authorization: Bearer <ui_token>" http://<host>:<port>/events— the initialdata:payload hasproviders[name].maskedKey; empty string means no active key loaded. - Hermes gateway log (inside container):
/root/.hermes/logs/gateway.log— shows all received Discord events. Zero entries after startup means the bot is connected but not receiving messages (stale gateway session or missingMESSAGE_CONTENTintent). - cllama context mount (
agentctx) currently holds onlyAgentsMD,ClawdapusMD, andMetadata(for bearer token auth). No outbound service credentials, no feed manifests, no decoration config. - cllama session history:
claw upbind-mounts.claw-session-history/→/claw/session-historyin the cllama container when cllama is enabled. cllama writes<dir>/<agent-id>/history.jsonl— one entry per successful 2xx completion. This is infrastructure-owned (proxy-written). Agents have no read API against it in Phase 1. Distinct from/claw/memory, which is runner-owned. Both surfaces are persistent across container restarts AND driver migrations (CLAW_TYPEchanges). - Provider API keys for cllama-managed services belong in
x-claw.cllama-env, not regular agentenvironment:blocks. - For cllama-enabled
count > 1services, bearer tokens and context are per ordinal, not per base service. compose.generated.ymlandDockerfile.generatedare generated artifacts. Inspect them, but do not hand-edit them as source.- OpenClaw config and cron paths are mounted as directories, not single files, because the runtime performs atomic rewrites.
- OpenClaw
openclaw health --jsoncan emit noise to stderr. The repo handles it as a stdout-first parse path. - cllama logger (
cllama/internal/logging/logger.go): fieldintervention *stringhas noomitempty— every event emits"intervention": null. Emittedtypevalues arerequest,response,error,intervention. Nodrift_scoreexists in the reference implementation. The spec (CLLAMA_SPEC.md§5) omitserrorfrom its type enum and usesintervention_reasonwhere the logger usesintervention. - 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 ownSOUL.mdtohermes-home/duringMaterializeto override this with the agent's contracted identity. Persona SOUL.md takes priority when configured. - Hermes
.envpassthrough: Container env vars from composeenvironment:are NOT available in Hermes agent tool execution. Only vars inallowedEnvPassthroughKeys()(internal/driver/hermes/config.go) reach the tool runtime via the.envfile. New env vars that agents need (e.g.CLAW_API_TOKEN) must be added to this list. - Pod-level
x-clawacceptspod,master,handles-defaults,cllama-defaults,surfaces-defaults,feeds-defaults,skills-defaults, andprincipals. Service-level fields inherit pod defaults; declaring a field replaces defaults unless...spread is used to extend. Seeexamples/master-claw/claw-pod.ymlfor the pattern. claw-api: selfon a service'sx-clawblock is an authority signal — it auto-generates a read-only bearer token scoped to that service and injectsCLAW_API_URL+CLAW_API_TOKEN. This is separate fromsurfaces: [service://claw-api]which only grants network reachability. Both are needed for full access.- claw-api principal scopes have four dimensions:
pods,services(base service names),claw_ids(ordinal IDs),compose_services(compose service names).compose_servicesis reserved for write-plane ordinal targeting (#78). Write verb constants (fleet.restartetc.) are defined but handlers are not yet implemented. sequential-conformance: trueat pod level allows services to share the same Discord handle ID (rollcall pattern). Without it, duplicate handle IDs across services are a hard error. Thecount > 1rejection is still enforced even in sequential-conformance pods.claw-wallis an infrastructure service auto-injected byclaw upwhen any cllama-enabled service has Discord channel IDs. The service nameclaw-wallis reserved — declaring it inclaw-pod.ymlis a hard error. Credentials are passed asCLAW_WALL_TOKENS=channelID:token,...pairs (not a map); the same channel ID can appear with different tokens for multi-bot pods. Cursor state is in-memory and resets on wall restart — agents may see some message overlap after redeploy.- Verb validation happens at parse time — unknown verbs in
x-claw.principalsorprincipals.jsonfail hard. claw.describeis the structured service descriptor label.claw upextracts.claw-describe.jsonfrom images (or falls back to the build context filesystem). Descriptors declare feeds, endpoints, auth, and skill file paths. Feed names from descriptors populate a pod-global feed registry; consumers subscribe by name via short-formfeeds: [name].- Feed resolution is two-phase: the parser stores short-form feed names as
FeedEntry{Unresolved: true}(no source/path validation).claw upresolves them after image inspection against the feed registry. Unresolved feeds that aren't in the registry are hard errors. - Spread expansion (
...) in pod default lists happens at the raw YAML layer inexpandPodDefaults(), BEFORE typed parsing (ParseSurface,parseFeeds). The typed parsers never see the...token. This ordering is critical — moving spread expansion after typed parsing will break.
- Lifecycle commands (
ps,logs,health,compose) refuse to run ifclaw-pod.ymlis newer thancompose.generated.yml.claw downis exempt — you can always tear down a stale pod. Runclaw upto regenerate. claw compose <subcommand> [args...]passes through todocker compose -f compose.generated.yml. Use it for any compose operation not covered by the named shortcuts (e.g.claw compose exec analyst bash,claw compose restart cllama-passthrough).HANDLEand channelSURFACEare different layers in current code.HANDLEis identity/bootstrap data; channelSURFACEis routing policy. If both are present, surface-level routing config is applied after handle defaults.- Map-form channel surfaces are still real code paths at the pod layer;
ClawBlock.Surfacesis parsed into[]driver.ResolvedSurface, not raw strings. - CLAWDAPUS.md is the single generated context document per agent. Surface metadata (service endpoints, channel config, handles) is inlined into CLAWDAPUS.md sections. Separate
surface-*.mdandhandle-*.mdskill files are no longer generated. Large service skill files (fromclaw.describeorclaw.skill.emit) are still mounted separately at/claw/skills/with a pointer in CLAWDAPUS.md. - OpenClaw cllama wiring does not write to
agents.defaults.model.baseURL/apiKey; the schema-valid rewrite path ismodels.providers.<provider>.{baseUrl,apiKey,api,models}. PERSONAis implemented as runtime materialization. Local refs are copied with traversal/symlink hardening; non-local refs are pulled as OCI artifacts.CLAW_PERSONA_DIRis only set when a persona is present.x-claw.includecontract composition is live.enforceandguidecontent is inlined into generatedAGENTS.md;referencecontent is mounted as read-only skill material.- The
Driverinterface (internal/driver/types.go) has four methods:Validate,Materialize,PostApply,HealthProbe. All run once at deploy/startup. There is no per-turn or per-request hook — any per-request context enrichment must go through cllama or a runner-native mechanism.
Current test layers:
- Unit:
go test ./... - Vet:
go vet ./... - Integration-tagged tests:
go test -tags integration ./... - Live/Docker spike tests:
go test -tags spike -run TestSpikeRollCall ./cmd/claw/...orgo test -tags spike -run TestSpikeComposeUp ./cmd/claw/...
Build tags currently present in the repo:
integrationspike
The spike tests are the heavy end-to-end path. They build images, run Docker, and in some cases require real Discord/provider credentials.
TestSpikeRollCallis the primary validation for cllama proxy enforcement. Every claw in the rollcall pod must make at least one real LLM call through cllama, andclaw auditmust show telemetry for all of them. If you change cllama wiring, driver materialization, feed injection, or telemetry normalization, this spike test is how you prove it works end-to-end.docs_quickstart_spike_test.goextracts shell blocks from README docs and runs them in a fresh Docker container. It removes infra images first to exercise the real pull path.
- Multi-arch cllama image:
docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/mostlydev/cllama:latest --push cllama/using themultiarch-builderbuildx builder. - User-defined
healthcheck:inclaw-pod.ymltakes precedence over driver defaults. The override happens incompose_emit.go— checkserviceOut["healthcheck"]before applyingresult.Healthcheck. Service.Composein the pod parser preserves all non-x-clawcompose keys as a deep-copiedmap[string]interface{}. This is how user healthchecks, depends_on, command, etc. flow through.- Releases: use
gh release createwith semver tags. cllama has its own tag namespace (e.g.v0.1.0) published from the submodule repo. ghcr.io packages default to private; must be set public via GitHub UI after first push. Pre-builtclawbinaries are published viagoreleaser(.goreleaser.ymlis in-tree) — do not suggest adding goreleaser, it already exists.install.shdownloads the latest release with checksum verification;claw updatere-runs it. claw-apiimage is not published to ghcr.io. TheensureImagefallback tries a git URL build which fails because the Docker builder cannot access the private cllama submodule. Build it locally from the repo root:docker build -t ghcr.io/mostlydev/claw-api:latest -f dockerfiles/claw-api/Dockerfile .claw-wallimage is built fromdockerfiles/claw-wall/Dockerfilewith.context and published toghcr.io/mostlydev/claw-wall:latest. TheensureInfraImagesfallback applies: local image →docker pull→ local Dockerfile build. Multi-arch build:docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/mostlydev/claw-wall:latest --push -f dockerfiles/claw-wall/Dockerfile .hermes-baseimage is built fromdockerfiles/hermes-base/and published toghcr.io/mostlydev/hermes-base:<tag>(e.g.v2026.3.17). It installshermes-agent[messaging,cron]from the pinned upstream tag, then runspatch-hermes-runtime.pyto apply compatibility fixes. The patch disables themembersandvoice_statesDiscord intents, makes slash-command sync non-blocking (best-effort with timeout), and — critically — setsallowed_mentions=discord.AllowedMentions(replied_user=False)on allchannel.send()calls that carry a reply reference. Without this last fix, Hermes's reply feature auto-pings the original author, which in multi-agent pods creates mention loops even whenDISCORD_REQUIRE_MENTION=true. Build:docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/mostlydev/hermes-base:v2026.3.17 --push dockerfiles/hermes-base/.x-claw.masterauto-injects aclaw-apiservice into compose. The master claw gets a bearer token baked into itsfeeds.jsonvia theauthfield, and aCLAW_API_URLenv var pointing at the in-pod API. Feed auth flows:claw up→feeds.jsonwithauth→ cllama fetcher sendsAuthorization: Bearerheader →claw-apivalidates via principals.json.- Alert thresholds are configurable via
CLAW_ALERT_*env vars on the host.claw upforwards them into the auto-injectedclaw-apicontainer. - Prefer reading the code paths above before relying on plan documents.
- When changing runtime behavior, update tests in the same area if they exist.
- If a behavior is reflected in generated artifacts, inspect both the source logic and the generated output expectations in tests.
- Be careful with the working tree: this repo is often mid-change, and unrelated files may already be modified.
- Site is a VitePress app at
site/. Not a submodule — it lives in this repo. - Auto-deploys to
clawdapus.devon merge to master whensite/**changes (.github/workflows/deploy-site.yml). - Sidebar/nav config:
site/.vitepress/config.mts. Guide pages:site/guide/. Top-level pages:site/index.md,site/manifesto.md,site/changelog.md. - Changelog:
site/changelog.mdis manually maintained. Release notes do NOT sync from GitHub releases automatically — add an entry tosite/changelog.mdwhen cutting a release or landing significant features on master.