diff --git a/cmd/claw-wall/store.go b/cmd/claw-wall/store.go index e9d61d3..09b2f8c 100644 --- a/cmd/claw-wall/store.go +++ b/cmd/claw-wall/store.go @@ -17,7 +17,7 @@ const ( backgroundContextSize = 10 defaultTailLimit = 40 defaultAwarenessLimit = 60 - defaultTailMaxChars = 8 * 1024 + defaultTailMaxChars = 32 * 1024 ) const ( diff --git a/cmd/claw/compose_up.go b/cmd/claw/compose_up.go index 2a8827b..81bba69 100644 --- a/cmd/claw/compose_up.go +++ b/cmd/claw/compose_up.go @@ -49,7 +49,7 @@ const ( conversationWallFeedSince = "24h" conversationWallFeedLimit = 40 conversationWallAwarenessLimit = 60 - conversationWallFeedMaxChars = 8 * 1024 + conversationWallFeedMaxChars = 32 * 1024 conversationWallBufferLimit = 5000 conversationWallPollInterval = "30" conversationWallRetention = "24h" @@ -1810,7 +1810,7 @@ func injectConversationWall(p *pod.Pod, resolvedClaws map[string]*driver.Resolve continue } settings := conversationWallChannelContext(p, svc) - awarenessSettings := conversationWallChannelAwarenessContext() + awarenessSettings := conversationWallChannelAwarenessContext(p, svc) svc.Claw.Feeds = appendConversationWallAwarenessFeed(svc.Claw.Feeds, channelIDs, awarenessSettings) svc.Claw.Feeds = appendConversationWallFeed(svc.Claw.Feeds, channelIDs, settings) svc.Claw.Tools = appendConversationWallToolPolicy(svc.Claw.Tools) @@ -1898,12 +1898,19 @@ func conversationWallChannelContext(p *pod.Pod, svc *pod.Service) conversationWa return settings } -func conversationWallChannelAwarenessContext() conversationWallContextSettings { - return conversationWallContextSettings{ +func conversationWallChannelAwarenessContext(p *pod.Pod, svc *pod.Service) conversationWallContextSettings { + settings := conversationWallContextSettings{ Since: conversationWallFeedSince, Limit: conversationWallAwarenessLimit, MaxChars: conversationWallFeedMaxChars, } + if p != nil && p.Context != nil { + settings = applyConversationWallChannelContext(settings, p.Context.Channel) + } + if svc != nil && svc.Claw != nil && svc.Claw.Context != nil { + settings = applyConversationWallChannelContext(settings, svc.Claw.Context.Channel) + } + return settings } func applyConversationWallChannelContext(settings conversationWallContextSettings, cfg *pod.ChannelContextConfig) conversationWallContextSettings { diff --git a/cmd/claw/compose_up_test.go b/cmd/claw/compose_up_test.go index 9f937e3..57a105d 100644 --- a/cmd/claw/compose_up_test.go +++ b/cmd/claw/compose_up_test.go @@ -3209,7 +3209,7 @@ func TestBuildFeedManifestUsesOrdinalClawID(t *testing.T) { { Name: conversationWallFeedName, Source: conversationWallServiceName, - Path: "/channel-context?consumer={claw_id}&channels=chan-a,chan-b&mode=tail&since=24h&limit=40&max_chars=8192", + Path: "/channel-context?consumer={claw_id}&channels=chan-a,chan-b&mode=tail&since=24h&limit=40&max_chars=32768", TTL: conversationWallFeedTTL, }, }, @@ -3225,10 +3225,10 @@ func TestBuildFeedManifestUsesOrdinalClawID(t *testing.T) { if len(feeds) != 1 { t.Fatalf("expected one feed, got %d", len(feeds)) } - if feeds[0].Path != "/channel-context?consumer=trader-1&channels=chan-a,chan-b&mode=tail&since=24h&limit=40&max_chars=8192" { + if feeds[0].Path != "/channel-context?consumer=trader-1&channels=chan-a,chan-b&mode=tail&since=24h&limit=40&max_chars=32768" { t.Fatalf("expected ordinal claw_id substitution, got %q", feeds[0].Path) } - if feeds[0].URL != "http://claw-wall:8080/channel-context?consumer=trader-1&channels=chan-a,chan-b&mode=tail&since=24h&limit=40&max_chars=8192" { + if feeds[0].URL != "http://claw-wall:8080/channel-context?consumer=trader-1&channels=chan-a,chan-b&mode=tail&since=24h&limit=40&max_chars=32768" { t.Fatalf("expected ordinal wall URL, got %q", feeds[0].URL) } } @@ -3938,14 +3938,14 @@ func TestInjectConversationWallAddsServiceAndFeed(t *testing.T) { if awarenessFeed.Source != conversationWallServiceName { t.Fatalf("expected claw-wall awareness source, got %+v", awarenessFeed) } - if awarenessFeed.Path != "/channel-awareness?channels=chan-1,chan-2&since=24h&limit=60&max_chars=8192&context_kind=raw_window" { + if awarenessFeed.Path != "/channel-awareness?channels=chan-1,chan-2&since=24h&limit=60&max_chars=32768&context_kind=raw_window" { t.Fatalf("unexpected awareness feed path: %q", awarenessFeed.Path) } contextFeed := testFeedByName(t, traderFeeds, conversationWallFeedName) if contextFeed.Source != conversationWallServiceName { t.Fatalf("expected claw-wall context source, got %+v", contextFeed) } - if contextFeed.Path != "/channel-context?consumer={claw_id}&channels=chan-1,chan-2&mode=tail&since=24h&limit=40&max_chars=8192" { + if contextFeed.Path != "/channel-context?consumer={claw_id}&channels=chan-1,chan-2&mode=tail&since=24h&limit=40&max_chars=32768" { t.Fatalf("unexpected wall feed path: %q", contextFeed.Path) } traderTools := p.Services["trader"].Claw.Tools @@ -3999,7 +3999,7 @@ func TestInjectConversationWallHonorsChannelContextConfig(t *testing.T) { t.Fatalf("expected trader feed, got %+v", traderFeeds) } traderAwareness := testFeedByName(t, traderFeeds, conversationWallAwarenessName) - if traderAwareness.Path != "/channel-awareness?channels=chan-1&since=24h&limit=60&max_chars=8192&context_kind=raw_window" { + if traderAwareness.Path != "/channel-awareness?channels=chan-1&since=6h&limit=25&max_chars=4096&context_kind=raw_window" { t.Fatalf("unexpected trader awareness path: %q", traderAwareness.Path) } traderContext := testFeedByName(t, traderFeeds, conversationWallFeedName) @@ -4012,7 +4012,7 @@ func TestInjectConversationWallHonorsChannelContextConfig(t *testing.T) { t.Fatalf("expected scout feed, got %+v", scoutFeeds) } scoutAwareness := testFeedByName(t, scoutFeeds, conversationWallAwarenessName) - if scoutAwareness.Path != "/channel-awareness?channels=chan-1&since=24h&limit=60&max_chars=8192&context_kind=raw_window" { + if scoutAwareness.Path != "/channel-awareness?channels=chan-1&since=30m&limit=8&max_chars=1024&context_kind=raw_window" { t.Fatalf("unexpected scout awareness path: %q", scoutAwareness.Path) } scoutContext := testFeedByName(t, scoutFeeds, conversationWallFeedName) diff --git a/cmd/claw/skill_data/SKILL.md b/cmd/claw/skill_data/SKILL.md index 565a495..0449712 100644 --- a/cmd/claw/skill_data/SKILL.md +++ b/cmd/claw/skill_data/SKILL.md @@ -389,7 +389,7 @@ The proxy sits between agents and LLM providers. Agents get bearer tokens, proxy ### claw-wall sidecar -Auto-injected by `claw up` when any cllama-enabled service has Discord channel IDs. Polls Discord channels and serves the recent channel transcript to agents through `channel-context` tail feeds; legacy unread-mailbox cursor paging remains available as `mode=delta`. On startup, wall backfills Discord history before its first forward poll up to `CLAW_WALL_RETENTION` (default `24h`) and `CLAW_WALL_BACKFILL_MAX_PAGES` (default `25`), while `CLAW_WALL_LIMIT` is a per-channel safety cap (default `5000`). Configure the generated tail window with pod or service `x-claw.context.channel` (`since`, `limit`, `max-chars`, `buffer`). Since `v0.15.0` channel-consuming services also get a default-on `channel-awareness` feed (uncursored 24h raw window, internally bounded) plus two cllama-mediated retrieval tools - `search_channel_context` and `get_channel_messages` - auto-subscribed via a compiler-owned claw-wall descriptor. Feed headers include `backfill_status`; `partial` or `rate_limited` means the backing window did not fully satisfy the requested horizon. Calls are gated by a generated per-agent channel allowlist, claw-wall service-token auth, and forwarded `X-Claw-ID`. The service name `claw-wall` is reserved - declaring it in `claw-pod.yml` is a hard error. +Auto-injected by `claw up` when any cllama-enabled service has Discord channel IDs. Polls Discord channels and serves the recent channel transcript to agents through `channel-context` tail feeds; legacy unread-mailbox cursor paging remains available as `mode=delta`. On startup, wall backfills Discord history before its first forward poll up to `CLAW_WALL_RETENTION` (default `24h`) and `CLAW_WALL_BACKFILL_MAX_PAGES` (default `25`), while `CLAW_WALL_LIMIT` is a per-channel safety cap (default `5000`). Configure the generated tail window with pod or service `x-claw.context.channel` (`since`, `limit`, `max-chars`, `buffer`). Since `v0.15.0` channel-consuming services also get a default-on `channel-awareness` feed (uncursored 24h raw window; `x-claw.context.channel.max-chars` tunes both feeds together, default 32 KB) plus two cllama-mediated retrieval tools - `search_channel_context` and `get_channel_messages` - auto-subscribed via a compiler-owned claw-wall descriptor. Feed headers include `backfill_status`; `partial` or `rate_limited` means the backing window did not fully satisfy the requested horizon. Calls are gated by a generated per-agent channel allowlist, claw-wall service-token auth, and forwarded `X-Claw-ID`. The service name `claw-wall` is reserved - declaring it in `claw-pod.yml` is a hard error. ## Generated Artifacts diff --git a/site/changelog.md b/site/changelog.md index ec1b7fa..1035f80 100644 --- a/site/changelog.md +++ b/site/changelog.md @@ -29,7 +29,7 @@ outline: deep ## Unreleased - +- **Channel feeds carry the whole conversation, not just the tail** — the default `max_chars` cap on both `channel-context` and `channel-awareness` feeds is raised from 8 KB to 32 KB so 24h on a modestly-active channel actually fits, and `channel-awareness` now honors the existing `x-claw.context.channel` tuning block (pod and service level). One knob raises both feeds together; busy production rooms can crank `max-chars` to 64-256 KB so the awareness surface covers a meaningful slice of the day rather than the last handful of messages. Closes [#242](https://github.com/mostlydev/clawdapus/issues/242). ## v0.17.1 {#v0-17-1} diff --git a/site/guide/pod-yaml.md b/site/guide/pod-yaml.md index 6e673db..a9590b4 100644 --- a/site/guide/pod-yaml.md +++ b/site/guide/pod-yaml.md @@ -211,20 +211,20 @@ services: ## Channel Context Tuning -When any cllama-enabled service has Discord channels in its handles, `claw up` auto-injects the `claw-wall` sidecar and a `channel-context` feed for each consuming agent. The feed serves the recent room transcript so a mentioned agent sees the conversation that prompted it (see [Social Topology · Channel Context Feed](/guide/social-topology#channel-context-feed-claw-wall)). Tune the generated tail with `x-claw.context.channel`: +When any cllama-enabled service has Discord channels in its handles, `claw up` auto-injects the `claw-wall` sidecar plus two feeds for each consuming agent: `channel-context` (cursored delta tail, mention/turn context) and `channel-awareness` (uncursored last-24h raw window, always-on memory of the room). Both feeds share the same tuning knob — see [Social Topology · Channel Context Feed](/guide/social-topology#channel-context-feed-claw-wall). Tune them with `x-claw.context.channel`: ```yaml x-claw: pod: trading-desk context: channel: - since: 24h # window covered by the tail (default 24h) - limit: 40 # max messages returned (default 40) - max-chars: 8192 # byte cap on rendered body (default 8192) - buffer: 5000 # per-channel safety cap in the wall sidecar (default 5000) + since: 24h # window covered by the feeds (default 24h) + limit: 40 # max messages returned (default: 40 for channel-context, 60 for channel-awareness) + max-chars: 32768 # byte cap on rendered body (default 32 KB) + buffer: 5000 # per-channel safety cap in the wall sidecar (default 5000) ``` -Service-level `x-claw.context.channel` overrides pod-level values for that service. `buffer` is shared across the pod, so when services disagree the largest value wins, with the built-in 5000-message floor still applied. `since` accepts any Go duration (`30m`, `2h15m`, `24h`). Set `max_chars` if you prefer the underscore alias; mixing the two with conflicting values is a parse error. +Service-level `x-claw.context.channel` overrides pod-level values for that service. The same block tunes both `channel-context` and `channel-awareness` — busy production rooms typically want `max-chars` somewhere in the 64-256 KB range so the 24h awareness feed actually covers a meaningful slice of the day rather than the last handful of messages. `buffer` is shared across the pod, so when services disagree the largest value wins, with the built-in 5000-message floor still applied. `since` accepts any Go duration (`30m`, `2h15m`, `24h`). Set `max_chars` if you prefer the underscore alias; mixing the two with conflicting values is a parse error. The wall serves `mode=tail` (non-consuming, latest-N walk) by default. Starting in v0.14.0, cllama drives the feed as a delta-since-watermark by adding `after=:` cursors to the URL, so each turn fetches only new messages instead of re-pasting the full tail. `since` and `limit` from `x-claw.context.channel` remain the bootstrap and dual-cap bounds. The wall's retention horizon and startup backfill budget are sidecar env settings (`CLAW_WALL_RETENTION`, `CLAW_WALL_BACKFILL_MAX_PAGES`) rather than pod-YAML schema. See [Social Topology · Cursored Append-Only Deltas](/guide/social-topology#cursored-append-only-deltas) for the proxy-side model. The legacy cursor/mailbox path remains callable as `mode=delta` for any consumer that genuinely wants oldest-unread paging; generated feeds do not use it. diff --git a/skills/clawdapus/SKILL.md b/skills/clawdapus/SKILL.md index 565a495..0449712 100644 --- a/skills/clawdapus/SKILL.md +++ b/skills/clawdapus/SKILL.md @@ -389,7 +389,7 @@ The proxy sits between agents and LLM providers. Agents get bearer tokens, proxy ### claw-wall sidecar -Auto-injected by `claw up` when any cllama-enabled service has Discord channel IDs. Polls Discord channels and serves the recent channel transcript to agents through `channel-context` tail feeds; legacy unread-mailbox cursor paging remains available as `mode=delta`. On startup, wall backfills Discord history before its first forward poll up to `CLAW_WALL_RETENTION` (default `24h`) and `CLAW_WALL_BACKFILL_MAX_PAGES` (default `25`), while `CLAW_WALL_LIMIT` is a per-channel safety cap (default `5000`). Configure the generated tail window with pod or service `x-claw.context.channel` (`since`, `limit`, `max-chars`, `buffer`). Since `v0.15.0` channel-consuming services also get a default-on `channel-awareness` feed (uncursored 24h raw window, internally bounded) plus two cllama-mediated retrieval tools - `search_channel_context` and `get_channel_messages` - auto-subscribed via a compiler-owned claw-wall descriptor. Feed headers include `backfill_status`; `partial` or `rate_limited` means the backing window did not fully satisfy the requested horizon. Calls are gated by a generated per-agent channel allowlist, claw-wall service-token auth, and forwarded `X-Claw-ID`. The service name `claw-wall` is reserved - declaring it in `claw-pod.yml` is a hard error. +Auto-injected by `claw up` when any cllama-enabled service has Discord channel IDs. Polls Discord channels and serves the recent channel transcript to agents through `channel-context` tail feeds; legacy unread-mailbox cursor paging remains available as `mode=delta`. On startup, wall backfills Discord history before its first forward poll up to `CLAW_WALL_RETENTION` (default `24h`) and `CLAW_WALL_BACKFILL_MAX_PAGES` (default `25`), while `CLAW_WALL_LIMIT` is a per-channel safety cap (default `5000`). Configure the generated tail window with pod or service `x-claw.context.channel` (`since`, `limit`, `max-chars`, `buffer`). Since `v0.15.0` channel-consuming services also get a default-on `channel-awareness` feed (uncursored 24h raw window; `x-claw.context.channel.max-chars` tunes both feeds together, default 32 KB) plus two cllama-mediated retrieval tools - `search_channel_context` and `get_channel_messages` - auto-subscribed via a compiler-owned claw-wall descriptor. Feed headers include `backfill_status`; `partial` or `rate_limited` means the backing window did not fully satisfy the requested horizon. Calls are gated by a generated per-agent channel allowlist, claw-wall service-token auth, and forwarded `X-Claw-ID`. The service name `claw-wall` is reserved - declaring it in `claw-pod.yml` is a hard error. ## Generated Artifacts