Skip to content
Open
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
9 changes: 7 additions & 2 deletions docs/content/docs/(configuration)/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,11 @@ Thresholds are fractions of `context_window`.
| `detached_worker_timeout_retry_limit` | integer | 2 | Retry limit before quarantining detached workers to backlog |
| `supervisor_kill_budget_per_tick` | integer | 8 | Max number of overdue processes supervisor may cancel per health tick |
| `circuit_breaker_threshold` | integer | 3 | Consecutive failures before auto-disable |
| `bulletin_interval_secs` | integer | 3600 | Legacy compatibility interval for bulletin-named synthesis surfaces |
| `bulletin_max_words` | integer | 1500 | Legacy compatibility word budget for bulletin-named synthesis surfaces |
| `bulletin_max_turns` | integer | 15 | Legacy compatibility max-turn budget for bulletin generation passes |
| `knowledge_synthesis_max_words` | integer | 500 | Target size for the long-term knowledge synthesis section |
| `knowledge_synthesis_debounce_secs` | integer | 60 | Delay after memory graph changes before regenerating knowledge synthesis |

### `[defaults.warmup]`

Expand All @@ -548,13 +553,13 @@ Thresholds are fractions of `context_window`.
| `refresh_secs` | integer | 900 | Seconds between background warmup passes |
| `startup_delay_secs` | integer | 5 | Delay before first warmup pass after boot |

When warmup is enabled, it is the primary bulletin refresh path. The cortex runtime loop still performs fallback bulletin/profile refresh when warmup is disabled or when the cached bulletin is stale (`bulletin_age_secs >= max(1, warmup.refresh_secs)`).
When warmup is enabled, it makes sure embeddings and the initial knowledge synthesis are ready before traffic. The older `bulletin_*` config and status names still exist as compatibility surfaces, but the user-facing prompt prefers `## Knowledge Context` plus the working-memory layers.

Dispatch readiness is derived from warmup runtime state:

- warmup state must be `warm`
- embedding must be ready
- bulletin age must be fresh (`<= max(60s, refresh_secs * 2)`)
- knowledge synthesis must have been generated at least once

When branch/worker/cron dispatch happens before readiness is satisfied, Spacebot still dispatches, increments cold-dispatch metrics, and queues a forced warmup pass in the background.
Comment on lines +556 to 564
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid implying warmup blocks all traffic.

“Ready before traffic” conflicts with Line 564, which says Spacebot still dispatches when readiness is not satisfied. The generated-at-least-once readiness condition is good; just soften the warmup guarantee.

📝 Proposed wording
-When warmup is enabled, it makes sure embeddings and the initial knowledge synthesis are ready before traffic. The older `bulletin_*` config and status names still exist as compatibility surfaces, but the user-facing prompt prefers `## Knowledge Context` plus the working-memory layers.
+When warmup is enabled, it prepares embeddings and triggers initial knowledge synthesis for dispatch readiness. The older `bulletin_*` config and status names still exist as compatibility surfaces, but the user-facing prompt prefers `## Knowledge Context` plus the working-memory layers.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
When warmup is enabled, it makes sure embeddings and the initial knowledge synthesis are ready before traffic. The older `bulletin_*` config and status names still exist as compatibility surfaces, but the user-facing prompt prefers `## Knowledge Context` plus the working-memory layers.
Dispatch readiness is derived from warmup runtime state:
- warmup state must be `warm`
- embedding must be ready
- bulletin age must be fresh (`<= max(60s, refresh_secs * 2)`)
- knowledge synthesis must have been generated at least once
When branch/worker/cron dispatch happens before readiness is satisfied, Spacebot still dispatches, increments cold-dispatch metrics, and queues a forced warmup pass in the background.
When warmup is enabled, it prepares embeddings and triggers initial knowledge synthesis for dispatch readiness. The older `bulletin_*` config and status names still exist as compatibility surfaces, but the user-facing prompt prefers `## Knowledge Context` plus the working-memory layers.
Dispatch readiness is derived from warmup runtime state:
- warmup state must be `warm`
- embedding must be ready
- knowledge synthesis must have been generated at least once
When branch/worker/cron dispatch happens before readiness is satisfied, Spacebot still dispatches, increments cold-dispatch metrics, and queues a forced warmup pass in the background.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/content/docs/`(configuration)/config.mdx around lines 556 - 564, The
copy currently implies warmup blocks traffic; update the paragraph mentioning
"ready before traffic" to instead state that warmup attempts to ensure
embeddings and initial knowledge synthesis are prepared before normal traffic
but does not block dispatch: keep references to the legacy bulletin_* names and
the user-facing "## Knowledge Context" prompt, retain the readiness conditions
(warmup state == 'warm', embedding ready, knowledge synthesis generated at least
once), and explicitly note that if readiness isn't met Spacebot still
dispatches, increments cold-dispatch metrics, and queues a forced warmup in the
background (preserve the term "cold-dispatch" and the behavior described later).


Expand Down
14 changes: 7 additions & 7 deletions docs/content/docs/(core)/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ Five process types, each implemented as a Rig `Agent<SpacebotModel, SpacebotHook
├─────────────────────────────────────────────────────────┤
│ Cortex │
│ System-level observer. Sees across all channels. │
Generates the memory bulletin — an LLM-curated
briefing injected into every channel's prompt.
Maintains working memory and knowledge synthesis for
channel prompt context.
└─────────────────────────────────────────────────────────┘
```

Expand Down Expand Up @@ -160,7 +160,7 @@ The primary database. Stores everything that benefits from relational queries:
| `cron_executions` | Execution history for cron jobs |
| `worker_runs` | Worker execution history with transcripts |
| `branch_runs` | Branch execution history |
| `cortex_events` | Cortex action log (bulletin generations, maintenance) |
| `cortex_events` | Cortex action log (knowledge synthesis, maintenance) |
| `cortex_chat_messages` | Persistent admin chat with cortex |
| `tasks` | Structured task board (backlog → in_progress → done) |
| `ingestion_progress` | Chunk-level progress for file ingestion |
Expand Down Expand Up @@ -316,7 +316,7 @@ Rig defaults to 0 (single call). Spacebot sets explicit limits per process type:
| Branch | 10 | A few iterations to think, recall, and conclude. |
| Worker | 50 | Many iterations for complex tasks. Segmented into 25-turn blocks. |
| Compactor | 10 | Summarize and extract memories. Bounded. |
| Cortex | 10 | Bulletin generation. Single-pass with tool calls. |
| Cortex | 10 | Knowledge synthesis generation. Single-pass with tool calls. |

## Control API

Expand All @@ -329,7 +329,7 @@ An embedded Axum HTTP server provides the control API for the dashboard and exte
| Agents | `/api/agents` | CRUD for agent definitions |
| Channels | `/api/channels` | Channel listing, history, settings, deletion |
| Workers | `/api/workers` | Worker status, history, timeline |
| Cortex | `/api/cortex` | Bulletin, profile, cortex chat |
| Cortex | `/api/cortex` | Knowledge synthesis, profile, cortex chat |
| Memory | `/api/memories` | Memory CRUD, graph queries |
| Config | `/api/config` | Runtime configuration read/write |
| Providers | `/api/providers` | LLM provider key management |
Expand Down Expand Up @@ -372,8 +372,8 @@ CLI (clap) → parse args
→ Best-effort startup warmup pass (bounded wait)
→ Initialize MessagingManager (start all platform adapters)
→ Initialize CronScheduler
→ Start Cortex loops (warmup, bulletin fallback, association, ready-task)
→ Register agent in active agents map
→ Start Cortex loops (warmup, knowledge synthesis, association, ready-task)
→ Register agent in active agents map
→ Enter main event loop (tokio::select!)
→ Inbound messages → route to Channel instances
→ Agent registration/removal
Expand Down
6 changes: 3 additions & 3 deletions docs/content/docs/(core)/channels.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ CREATE TABLE channels (
| `platform` | TEXT | Extracted from the ID prefix: `discord`, `slack`, `cron`, `webhook` |
| `display_name` | TEXT | Human-readable name from platform metadata (e.g. `#general`) |
| `platform_meta` | TEXT (JSON) | Platform-specific metadata blob |
| `bulletin` | TEXT | Reserved for per-channel memory bulletins |
| `bulletin` | TEXT | Reserved for legacy per-channel memory context |
| `permissions` | TEXT (JSON) | Reserved for per-channel permission overrides |
| `is_active` | INTEGER | 1 = active, 0 = archived |
| `created_at` | TIMESTAMP | When the channel was first seen |
Expand Down Expand Up @@ -200,9 +200,9 @@ Implementation: `src/conversation/settings.rs` — `ResolvedConversationSettings

Two columns exist in the schema but aren't populated yet:

**`bulletin`** — Per-channel memory bulletins. Currently the cortex generates a single agent-wide bulletin stored in `RuntimeConfig::memory_bulletin` via `ArcSwap`. The `bulletin` column is the hook for generating channel-specific bulletins that incorporate conversation-local context alongside the global memory graph.
**`bulletin`** -- Reserved for legacy per-channel memory context. Channel prompts now prefer layered working memory, channel activity, participant context, and `## Knowledge Context` from `RuntimeConfig::knowledge_synthesis`.

**`permissions`** Per-channel permission overrides as JSON. Intended for the UI layer controlling which users can interact with the agent in specific channels, rate limits, tool restrictions, etc.
**`permissions`** -- Per-channel permission overrides as JSON. Intended for the UI layer: controlling which users can interact with the agent in specific channels, rate limits, tool restrictions, etc.

## Implementation

Expand Down
19 changes: 9 additions & 10 deletions docs/content/docs/(core)/compaction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ LLM context windows are finite. A long conversation fills up. In OpenClaw, when

## How It Works

The compactor is a programmatic monitor not an LLM process. It watches a channel's context size (estimated token count) and triggers compaction workers in the background. The channel keeps responding to messages the entire time.
The compactor is a programmatic monitor, not an LLM process. It watches a channel's context size (estimated token count) and triggers compaction workers in the background. The channel keeps responding to messages the entire time.

Every turn, after the channel's LLM call completes, the compactor checks context usage:

Expand Down Expand Up @@ -42,15 +42,14 @@ Only one compaction runs at a time per channel. If context is already being comp

These are the normal path. A compaction worker runs in `tokio::spawn` alongside the channel:

1. **Drain** Write-lock the channel's history, remove the oldest N messages (30% for background, 50% for aggressive). Release the lock. The channel can immediately continue with the remaining history.
1. **Drain** -- Write-lock the channel's history, remove the oldest N messages (30% for background, 50% for aggressive). Release the lock. The channel can immediately continue with the remaining history.

2. **Summarize** Build a transcript from the removed messages and run a Rig agent with `prompts/en/compactor.md.j2` as the system prompt. The agent produces a condensed summary preserving key decisions, active topics, commitments, and emotional context. It discards greetings, tool call mechanics, and intermediate reasoning.
2. **Summarize** -- Build a transcript from the removed messages and run a one-turn Rig agent with `prompts/en/compactor.md.j2` as the system prompt. The agent produces a condensed summary preserving key decisions, active topics, commitments, emotional context, and active tasks. It discards greetings, tool call mechanics, and intermediate reasoning.

3. **Extract memories** — The compaction agent has access to the `memory_save` tool. While summarizing, it identifies facts, preferences, decisions, and observations worth keeping long-term and saves them directly to the memory store. These persist independently of the conversation.
3. **Inject summary** -- Write-lock the history again, insert the summary at position 0 as `[Compaction Summary]: ...`. Release the lock. The channel sees this summary on its next turn.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify that prepended summaries are newest-first.

Line 49 says the summary is inserted at position 0, so if multiple summaries remain, the newest summary appears before older summaries. The later “stack chronologically” wording/example should be adjusted or readers may expect oldest-to-newest ordering.

📝 Proposed doc wording
-| Multiple summaries | One summary replaces all | Summaries stack chronologically |
+| Multiple summaries | One summary replaces all | Summaries are prepended as rolling summaries |

Also applies to: 65-75, 116-118

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/content/docs/`(core)/compaction.mdx at line 49, Update the "Inject
summary" step in docs/content/docs/(core)/compaction.mdx to explicitly state
that summaries are prepended newest-first (inserting at position 0 means the
most recent summary appears before older ones); revise any phrases like "stack
chronologically" and the examples referenced around the other occurrences (the
blocks noted at ~lines 65-75 and 116-118) to reflect newest-to-oldest ordering
so readers won't expect oldest-first ordering.


4. **Inject summary** — Write-lock the history again, insert the summary at position 0 as `[Compaction Summary]: ...`. Release the lock. The channel sees this summary on its next turn.
The compaction agent runs with `max_turns(1)`. It has no tool server and does not receive `memory_save`. Durable memory extraction is handled by persistence branches, direct memory tools, ingestion, and cortex workflows.

The compaction agent runs with `max_turns(10)` — enough for the LLM to produce the summary and call `memory_save` a few times for extracted memories.

## Emergency Truncation

Expand All @@ -77,13 +76,13 @@ This gives the channel rolling awareness of what happened without carrying the f

## What the Compaction LLM Sees

The compaction agent receives a rendered transcript of the removed messages. User messages, assistant responses, tool calls, and tool results — all formatted as readable text. The agent's system prompt (`prompts/en/compactor.md.j2`) tells it to:
The compaction agent receives a rendered transcript of the removed messages. User messages, assistant responses, tool calls, and tool results are formatted as readable text. The agent's system prompt (`prompts/en/compactor.md.j2`) tells it to:

**Preserve:** Key decisions, active topics, commitments, emotional context, active workers/tasks.

**Discard:** Greetings, small talk, tool call details (results matter, not mechanics), intermediate reasoning, repeated information.

**Extract as memories:** Facts, preferences, decisions, observations — anything that should outlive the conversation.
It does not extract memories. The compactor's output is only the summary that replaces older history.

## Configuration

Expand Down Expand Up @@ -114,8 +113,8 @@ The `context_window` setting (default 128,000 tokens) determines the denominator
| When it runs | Blocks the session | Background tokio task |
| User experience | Typing indicator, 20s freeze | No interruption |
| Summarization | Same session's LLM | Dedicated compaction worker |
| Memory extraction | Separate pass | Same LLM call as summarization |
| Raw transcript | Lost | Extracted as memories |
| Memory extraction | Separate pass | Separate persistence paths (branches, tools, ingestion, cortex workflows) |
| Raw transcript | Lost | Replaced by rolling summaries |
| Multiple summaries | One summary replaces all | Summaries stack chronologically |
| Emergency fallback | None (just hope it fits) | Hard truncation at 95% |

Expand Down
Loading
Loading