Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ When a reasoning model tries to govern itself, the guardrails are part of the sa
- **Audit logging:** Structured JSON on stdout — timestamp, agent, model, latency, tokens, cost, intervention reason.
- **Managed tool mediation:** Services declare callable tools via `claw.describe` (MCP-shaped schemas). `claw up` compiles per-agent `tools.json`. cllama injects tools into LLM requests, intercepts `tool_call` responses, executes them against the service, and loops until terminal text — transparent to the runner. HTTP services, Streamable HTTP MCP sidecars, and stdio MCP servers wrapped by `claw-mcp-stdio` are supported.
- **Ambient memory plane:** Services declare `recall`, `retain`, and `forget` endpoints via `claw.describe`. `claw up` compiles per-agent `memory.json`. cllama calls `/recall` before each inference turn and `/retain` after each successful response (async, non-blocking). Memory intelligence stays in swappable external services — the proxy owns orchestration only.
- **Context blocks:** Operators can compile short per-agent context snippets into `context-blocks.json`; cllama injects enabled blocks into late runtime context so durable operating focus stays visible without expanding the primary contract.
- **Operator dashboard:** Real-time web UI at host port 8181 by default (container `:8081`) — agent activity, provider status, cost breakdown.

The reference implementation is [`cllama`](https://github.com/mostlydev/cllama) — a zero-dependency Go binary that implements the transport layer (identity, routing, cost tracking, budget enforcement). Future proxy types (`cllama-policy`) will add bidirectional interception: evaluating outbound prompts and amending inbound responses against the agent's behavioral contract.
Expand Down Expand Up @@ -554,7 +555,7 @@ inline the service's advertised tools and the mount path:
Clawdapus is designed for autonomous fleet governance. The operator writes the `Clawfile` and sets the budgets, but day-to-day oversight can be delegated to a **Master Claw** — an AI governor.

**The Governance Proxy is its Sensory Organ:**
The `cllama` proxy is the programmatic choke point. It sits on the network, holds provider credentials, applies compiled model/tool/context/budget policy, rejects over-cap turns, and emits structured telemetry (cost, interventions, tool rounds). It doesn't "think" about management; it is a passive sensor and firewall.
The `cllama` proxy is the programmatic choke point. It sits on the network, holds provider credentials, applies compiled model/tool/context/budget policy, rejects over-cap turns, injects compiled context blocks, and emits structured telemetry (cost, interventions, tool rounds). It doesn't "think" about management; it is a passive sensor and firewall.

**The Master Claw is the Brain:**
The Master Claw is an actual LLM-powered agent running in the pod, reading proxy telemetry and acting on it. `x-claw.master` wires this today: it auto-injects a `claw-api` service and hands the governor a scoped bearer token and `CLAW_API_URL`, so it can read fleet telemetry and act through an authenticated, scope-checked API. The executive policy it runs — shifting enforced budget caps, quarantining a high-cost or off-policy agent, promoting a recipe — is operator-defined (recipe promotion is still on the roadmap).
Expand Down
39 changes: 23 additions & 16 deletions cmd/claw-api/agent_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ type agentIndexEntry struct {
}

type agentContractResponse struct {
ClawID string `json:"claw_id"`
AgentsMD string `json:"agents_md"`
ClawdapusMD string `json:"clawdapus_md"`
Metadata any `json:"metadata"`
Feeds any `json:"feeds"`
Tools any `json:"tools"`
Memory any `json:"memory"`
ServiceAuth map[string]any `json:"service_auth,omitempty"`
ClawID string `json:"claw_id"`
AgentsMD string `json:"agents_md"`
ClawdapusMD string `json:"clawdapus_md"`
Metadata any `json:"metadata"`
Feeds any `json:"feeds"`
Tools any `json:"tools"`
Memory any `json:"memory"`
ContextBlocks any `json:"context_blocks"`
ServiceAuth map[string]any `json:"service_auth,omitempty"`
}

type agentContextPath struct {
Expand Down Expand Up @@ -134,21 +135,27 @@ func (h *apiHandler) handleAgentContract(w http.ResponseWriter, agentID string)
writeJSONError(w, http.StatusInternalServerError, err.Error())
return
}
contextBlocks, err := readJSONArtifact(filepath.Join(agentDir, "context-blocks.json"), true)
if err != nil {
writeJSONError(w, http.StatusInternalServerError, err.Error())
return
}
serviceAuth, err := readServiceAuthArtifacts(filepath.Join(agentDir, "service-auth"))
if err != nil {
writeJSONError(w, http.StatusInternalServerError, err.Error())
return
}

writeJSON(w, http.StatusOK, agentContractResponse{
ClawID: agentID,
AgentsMD: string(agentsMD),
ClawdapusMD: string(clawdapusMD),
Metadata: redactJSONValue(metadata),
Feeds: redactJSONValue(feeds),
Tools: redactJSONValue(tools),
Memory: redactJSONValue(memory),
ServiceAuth: redactServiceAuthArtifacts(serviceAuth),
ClawID: agentID,
AgentsMD: string(agentsMD),
ClawdapusMD: string(clawdapusMD),
Metadata: redactJSONValue(metadata),
Feeds: redactJSONValue(feeds),
Tools: redactJSONValue(tools),
Memory: redactJSONValue(memory),
ContextBlocks: redactJSONValue(contextBlocks),
ServiceAuth: redactServiceAuthArtifacts(serviceAuth),
})
}

Expand Down
8 changes: 8 additions & 0 deletions cmd/claw-api/agent_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ func TestAgentContractRedactsContextCredentials(t *testing.T) {
if err := os.WriteFile(filepath.Join(agentDir, "memory.json"), []byte(`{"service":"mem","auth":{"type":"bearer","token":"memory-token"}}`), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(agentDir, "context-blocks.json"), []byte(`{"version":1,"blocks":[{"id":"focus","kind":"runtime_motivation","text":"Stay on the active operating contract.","enabled":true,"placement":"after_feeds","cadence":"every_turn","max_chars":800}]}`), 0o644); err != nil {
t.Fatal(err)
}
authDir := filepath.Join(agentDir, "service-auth")
if err := os.MkdirAll(authDir, 0o700); err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -147,6 +150,11 @@ func TestAgentContractRedactsContextCredentials(t *testing.T) {
if clawAPIAuth["token"] != "[REDACTED]" {
t.Fatalf("service-auth token was not redacted: %+v", clawAPIAuth)
}
contextBlocks := resp["context_blocks"].(map[string]any)
blocks := contextBlocks["blocks"].([]any)
if blocks[0].(map[string]any)["id"] != "focus" || blocks[0].(map[string]any)["kind"] != "runtime_motivation" {
t.Fatalf("context blocks were not returned: %+v", contextBlocks)
}
}

func TestAgentContractPrefersEffectiveAgentsMD(t *testing.T) {
Expand Down
31 changes: 31 additions & 0 deletions cmd/claw/compose_up.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ func runComposeUp(podFile string) (err error) {
Tools: tools,
ToolPolicy: agentToolPolicy(p, name),
Memory: memory,
ContextBlocks: agentContextBlocks(p, name),
ServiceAuth: ordinalAuth,
ChannelAllowlist: conversationWallAllowlists[ordinalName],
Metadata: injectAgentBudget(cllama.InjectCompiledModelPolicy(map[string]any{
Expand Down Expand Up @@ -647,6 +648,7 @@ func runComposeUp(podFile string) (err error) {
Tools: tools,
ToolPolicy: agentToolPolicy(p, name),
Memory: memory,
ContextBlocks: agentContextBlocks(p, name),
ServiceAuth: svcAuth,
ChannelAllowlist: conversationWallAllowlists[name],
Metadata: injectAgentBudget(cllama.InjectCompiledModelPolicy(map[string]any{
Expand Down Expand Up @@ -1628,6 +1630,35 @@ func agentBudgetPolicy(p *pod.Pod, serviceName string) *cllama.BudgetPolicy {
}
}

func agentContextBlocks(p *pod.Pod, serviceName string) []cllama.ContextBlockManifestEntry {
if p == nil {
return nil
}
var blocks []pod.ContextBlockConfig
if p.Context != nil && p.Context.Blocks != nil {
blocks = p.Context.Blocks
}
if svc := p.Services[serviceName]; svc != nil && svc.Claw != nil && svc.Claw.Context != nil && svc.Claw.Context.Blocks != nil {
blocks = svc.Claw.Context.Blocks
}
if len(blocks) == 0 {
return nil
}
out := make([]cllama.ContextBlockManifestEntry, 0, len(blocks))
for _, block := range blocks {
out = append(out, cllama.ContextBlockManifestEntry{
ID: block.ID,
Kind: block.Kind,
Text: block.Text,
Enabled: block.Enabled,
Placement: block.Placement,
MaxChars: block.MaxChars,
Cadence: block.Cadence,
})
}
return out
}

func injectAgentBudget(meta map[string]any, budget *cllama.BudgetPolicy) map[string]any {
if budget != nil {
meta["budget"] = budget
Expand Down
52 changes: 52 additions & 0 deletions cmd/claw/context_blocks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"testing"

"github.com/mostlydev/clawdapus/internal/pod"
)

func TestAgentContextBlocksResolvesPodAndServiceConfig(t *testing.T) {
p := &pod.Pod{
Context: &pod.ContextConfig{
Blocks: []pod.ContextBlockConfig{{
ID: "pod-focus",
Kind: "runtime_motivation",
Text: "Pod block.",
Enabled: true,
Placement: "after_feeds",
MaxChars: 800,
Cadence: "every_turn",
}},
},
Services: map[string]*pod.Service{
"inherited": {Claw: &pod.ClawBlock{}},
"override": {Claw: &pod.ClawBlock{Context: &pod.ContextConfig{
Blocks: []pod.ContextBlockConfig{{
ID: "local-focus",
Kind: "feed_frame",
Text: "Local block.",
Enabled: false,
Placement: "before_feeds",
MaxChars: 400,
Cadence: "every_turn",
}},
}}},
"suppress": {Claw: &pod.ClawBlock{Context: &pod.ContextConfig{
Blocks: []pod.ContextBlockConfig{},
}}},
},
}

inherited := agentContextBlocks(p, "inherited")
if len(inherited) != 1 || inherited[0].ID != "pod-focus" || inherited[0].Kind != "runtime_motivation" || inherited[0].Placement != "after_feeds" || !inherited[0].Enabled {
t.Fatalf("expected inherited pod context block, got %+v", inherited)
}
override := agentContextBlocks(p, "override")
if len(override) != 1 || override[0].ID != "local-focus" || override[0].Kind != "feed_frame" || override[0].Enabled || override[0].MaxChars != 400 {
t.Fatalf("expected service context block override, got %+v", override)
}
if suppressed := agentContextBlocks(p, "suppress"); suppressed != nil {
t.Fatalf("expected empty service override to suppress pod context blocks, got %+v", suppressed)
}
}
18 changes: 16 additions & 2 deletions cmd/claw/skill_data/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ claw agent add [name] # add agent service to existing pod
claw audit [--since <dur>] [--claw <id>] [--type <type>] [--json]
# summarize cllama telemetry from container logs
# types: request, response, error, intervention,
# feed_fetch, provider_pool, tool_call
# feed_fetch, feed_injection, context_block,
# memory_op, provider_pool, tool_call
claw api schedule <subcommand> # inspect/control scheduled invocations via claw-api
# list | get <id> | pause <id> | resume <id> | skip-next <id> |
# clear-skip-next <id> | fire <id>
Expand Down Expand Up @@ -305,6 +306,12 @@ When a service subscribes to a memory service via `x-claw.memory`, cllama perfor

`claw up` compiles `memory.json` into each subscribing agent's cllama context directory with endpoint URLs, auth tokens, and timeout configuration.

### Context Blocks

Pod or service `x-claw.context.blocks` compiles short operator-authored snippets into each agent's cllama context directory as `context-blocks.json`. cllama injects enabled blocks into late runtime context before or after feeds on every turn, so durable operating focus stays visible without expanding the primary contract.

Supported fields: `id` (required), `text` (required), `enabled` (default `true`), `cadence` (`every_turn`), `placement` (`before_feeds` or `after_feeds`, default `after_feeds`), and `max-chars` / `max_chars` (default `800`). A service-level list replaces pod-level blocks; an explicit empty list suppresses inherited blocks.

### Managed Tool Mediation (v0.5.0)

When a service subscribes to tools via `x-claw.tools`, cllama performs bounded tool execution within the inference turn:
Expand Down Expand Up @@ -374,6 +381,7 @@ The proxy sits between agents and LLM providers. Agents get bearer tokens, proxy
CLAWDAPUS.md # infrastructure map
tools.json # managed tool manifest (when tools subscribed)
memory.json # memory service config (when memory subscribed)
context-blocks.json # context block manifest (when configured)
```

### Provider support
Expand Down Expand Up @@ -425,6 +433,7 @@ When the aggregate cap drops a feed the model sees an explicit `--- FEED: <name>
| `jobs.json` | Cron schedule for INVOKE tasks | Runner state directory |
| `tools.json` | Managed tool manifest per agent | cllama context directory |
| `memory.json` | Memory service config per agent | cllama context directory |
| `context-blocks.json` | Runtime context block manifest per agent | cllama context directory |

## Drivers

Expand Down Expand Up @@ -493,7 +502,7 @@ When a pod declares a `clawdash` surface, `claw up` publishes the operational da

- **Fleet / Topology** — running services, wiring, driver types.
- **Agents** — per-agent *contract* as compiled at `claw up` time (AGENTS.md, CLAWDAPUS.md, feed subscriptions, managed tools, memory wiring, metadata).
- **Agents → Live Context** — the system message, tools array, injected feeds, memory recall, time context, and interventions that were assembled for the most recent inference turn. Sourced from the cllama snapshot store (`/internal/context/<agent-id>/snapshot`, proxied through `claw-api`). Credentials and token fields are redacted.
- **Agents → Live Context** — the system message, tools array, context blocks, injected feeds, memory recall, time context, and interventions that were assembled for the most recent inference turn. Sourced from the cllama snapshot store (`/internal/context/<agent-id>/snapshot`, proxied through `claw-api`). Credentials and token fields are redacted.
- **Schedule** — `INVOKE` and `x-claw.invoke` cron entries, with `claw api schedule ...` controls.

All views are read-only and scoped through `claw-api` principals. Use this before log-diving — "what did the model actually see last turn" has a direct answer here.
Expand Down Expand Up @@ -521,6 +530,11 @@ All views are read-only and scoped through `claw-api` principals. Use this befor
- `claw memory forget --entry-id <id>` writes tombstones; subsequent backfills skip those entries
- Declaring `memory:` without `cllama:` is a hard error

### Context blocks not visible
- Check `context-blocks.json` in `.claw-runtime/context/<agent-id>/`
- Check `claw audit --type context_block` for injected or skipped context block entries
- Verify `cadence` is `every_turn`, `placement` is `before_feeds` or `after_feeds`, and the block text is within `max-chars`

## Working Examples

| Example | Path | What it demonstrates |
Expand Down
Loading
Loading