Skip to content
Closed
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
68 changes: 68 additions & 0 deletions METRICS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ cargo build --release --features metrics

The `[metrics]` config block is always parsed (so config validation works) but has no effect without the feature.

## Rig Alignment Telemetry Status

`[defaults.rig_alignment]` now emits dedicated `spacebot_rig_*` Prometheus metrics.

These metrics complement the existing request/tool metrics rather than replacing them:

- Existing request/tool metrics still cover end-to-end execution paths (`spacebot_llm_requests_total`, duration histograms, tool counters).
- `spacebot_rig_request_semantics_total` records whether `tool_choice` / `output_schema` were applied, shadowed, rejected, or unsupported.
- `spacebot_rig_stream_sessions_total` and `spacebot_rig_time_to_first_delta_ms` separate native vs synthetic streaming behavior and expose first-delta latency.
- `spacebot_rig_tool_concurrency_total` records the request-level concurrency chosen for builtin workers.
- `spacebot_rig_drift_detected_total` records semantics requests that the custom Rig bridge could not apply cleanly.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Metric Inventory

All metrics are prefixed with `spacebot_`. The registry uses a private `prometheus::Registry` (not the default global one) to avoid conflicts with other libraries.
Expand Down Expand Up @@ -60,6 +72,50 @@ All metrics are prefixed with `spacebot_`. The registry uses a private `promethe

**Cardinality:** 1 series.

#### `spacebot_rig_request_semantics_total`

| Field | Value |
|-------|-------|
| Type | `IntCounterVec` |
| Labels | `process`, `provider`, `field`, `decision` |
| Instrumented in | `src/llm/model.rs` — `SpacebotModel::evaluate_request_semantics()` |
| Description | Request-semantic decisions for `tool_choice` and `output_schema`. `decision` is one of `applied`, `shadowed`, `rejected`, or `unsupported`. |

**Cardinality:** `processes × providers × 2 fields × 4 decisions`.

#### `spacebot_rig_stream_sessions_total`

| Field | Value |
|-------|-------|
| Type | `IntCounterVec` |
| Labels | `process`, `provider`, `mode` |
| Instrumented in | `src/llm/model.rs` — `SpacebotModel::stream()` |
| Description | Streaming session starts. `mode` is `native` for provider-backed streaming and `synthetic` when Spacebot wraps a non-streaming completion into Rig streaming events. |

**Cardinality:** `processes × providers × 2 modes`.

#### `spacebot_rig_tool_concurrency_total`

| Field | Value |
|-------|-------|
| Type | `IntCounterVec` |
| Labels | `worker_type`, `concurrency` |
| Instrumented in | `src/agent/worker.rs` — builtin worker startup |
| Description | Request-level tool concurrency chosen for worker prompts after allowlist validation and runtime clamping. |

**Cardinality:** `worker_types × concurrency values`.

#### `spacebot_rig_drift_detected_total`

| Field | Value |
|-------|-------|
| Type | `IntCounterVec` |
| Labels | `surface` |
| Instrumented in | `src/llm/model.rs` — `SpacebotModel::evaluate_request_semantics()` |
| Description | Counts semantic requests that the custom Rig bridge could not apply cleanly and therefore shadowed, rejected, or treated as unsupported. |

**Cardinality:** one series per drift surface (currently `tool_choice`, `output_schema`).

#### `spacebot_llm_tokens_total`

| Field | Value |
Expand Down Expand Up @@ -120,6 +176,18 @@ All metrics are prefixed with `spacebot_`. The registry uses a private `promethe

**Cardinality:** Same as `spacebot_llm_requests_total` (per-bucket overhead is fixed, not per-series).

#### `spacebot_rig_time_to_first_delta_ms`

| Field | Value |
|-------|-------|
| Type | `HistogramVec` |
| Labels | `process`, `provider` |
| Buckets | 25, 50, 100, 250, 500, 1000, 1500, 2500, 3500, 5000, 10000, 30000 |
| Instrumented in | `src/llm/model.rs` — instrumented streaming result wrapper |
| Description | Milliseconds from stream start until the first non-empty text delta is emitted. Covers both native and synthetic streaming paths. |

**Cardinality:** `processes × providers`.

#### `spacebot_tool_call_duration_seconds`

| Field | Value |
Expand Down
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,57 @@ channel = "my-provider/my-model"

Additional built-in providers include **Kilo Gateway**, **OpenCode Go**, **NVIDIA**, **MiniMax**, **Moonshot AI (Kimi)**, and **Z.AI Coding Plan** — configure with `kilo_key`, `opencode_go_key`, `nvidia_key`, `minimax_key`, `moonshot_key`, or `zai_coding_plan_key` in `[llm]`.

### Rig Alignment Controls

Spacebot includes a `defaults.rig_alignment` block for request-semantics forwarding, channel/cortex streaming, and read-only worker tool concurrency.

- **Request semantics mode** — `off`, `shadow`, or `enforced` for `tool_choice` and `output_schema` forwarding.
- **Provider allowlists** — defaults allow `openai`, `openai-chatgpt`, and `anthropic` for `tool_choice` and `output_schema`.
- **Streaming toggles** — `channel_streaming` and `cortex_chat_streaming` are opt-in and disabled by default.
- **Worker concurrency guardrails** — concurrency only applies when the exposed tool set is fully read-only and allowlisted; otherwise Spacebot forces sequential execution.

```toml
[defaults.rig_alignment]
request_semantics_mode = "shadow" # "off" | "shadow" | "enforced"
channel_streaming = false
cortex_chat_streaming = false
worker_read_only_tool_concurrency = 1 # standard workers still stay sequential unless the exposed surface is fully read-only
worker_max_tool_concurrency = 4
worker_read_only_tool_allowlist = [
"read_skill",
"spacebot_docs",
"memory_recall",
"channel_recall",
"web_search",
"worker_inspect",
"email_search",
"config_inspect"
]
output_schema_provider_allowlist = ["openai", "openai-chatgpt", "anthropic"]
tool_choice_provider_allowlist = ["openai", "openai-chatgpt", "anthropic"]
```

In `enforced` mode, unsupported semantics fail closed; in `shadow` mode they are skipped with structured decision logs.

The default worker surface still exposes mutating tools such as `shell`, `file`, `exec`, `task_update`, and `set_status`, so `worker_read_only_tool_concurrency > 1` only takes effect for specialized worker surfaces that expose solely allowlisted read-only tools.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Rig-alignment observability is exposed through dedicated `spacebot_rig_*` Prometheus metrics documented in [METRICS.md](./METRICS.md), plus structured logs for request-semantic decisions, stream mode, first-delta latency, worker concurrency, and terminal delivery reason.

For rollout, use the staged runbook in [`docs/content/docs/(configuration)/config.mdx`](./docs/content/docs/(configuration)/config.mdx): start with `request_semantics_mode = "shadow"`, then enforce in staging, then canary `channel_streaming`, `cortex_chat_streaming`, and worker concurrency one step at a time.

When bumping Rig, rerun the rig-alignment parity suite before merging:

```bash
cargo test forwards_ -- --nocapture
cargo test rejects_unsupported_semantics_enforced -- --nocapture
cargo test channel_streaming_reply_terminal -- --nocapture
cargo test cortex_chat_streaming_persists_final -- --nocapture
cargo test adapter_streaming_throttle -- --nocapture
cargo test worker_concurrency_allowlist -- --nocapture
cargo test worker_concurrency_reduces_wall_clock -- --nocapture
cargo test rig_parity -- --nocapture
```

### Skills

Extensible skill system integrated with [skills.sh](https://skills.sh):
Expand Down
153 changes: 153 additions & 0 deletions docs/content/docs/(configuration)/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,26 @@ coding = "anthropic/claude-sonnet-4-20250514"
[defaults.routing.fallbacks]
"anthropic/claude-sonnet-4-20250514" = ["anthropic/claude-haiku-4.5-20250514"]

# Rig request-semantics and streaming/concurrency alignment controls.
[defaults.rig_alignment]
request_semantics_mode = "shadow" # "off" | "shadow" | "enforced"
channel_streaming = false # stream channel model deltas through adapter placeholders
cortex_chat_streaming = false # stream cortex chat SSE `text_delta` events
worker_read_only_tool_concurrency = 1 # standard workers still stay sequential unless the exposed surface is fully read-only
worker_max_tool_concurrency = 4 # hard cap for per-request tool concurrency
worker_read_only_tool_allowlist = [
"read_skill",
"spacebot_docs",
"memory_recall",
"channel_recall",
"web_search",
"worker_inspect",
"email_search",
"config_inspect"
]
output_schema_provider_allowlist = ["openai", "openai-chatgpt", "anthropic"]
tool_choice_provider_allowlist = ["openai", "openai-chatgpt", "anthropic"]

# Context compaction thresholds (fraction of context_window).
[defaults.compaction]
background_threshold = 0.80 # background summarization
Expand Down Expand Up @@ -249,6 +269,7 @@ Most config values are hot-reloaded when their files change. Spacebot watches `c
| `max_concurrent_branches` | Yes | Next branch spawn checks new limit |
| Browser config | Yes | Next worker spawn uses new config |
| Warmup config | Yes | Next warmup pass uses new values |
| Rig-alignment config | Yes | Next LLM request/worker turn reads new semantics, streaming, and tool-concurrency settings |
| Identity files (SOUL.md, etc.) | Yes | Next channel message renders new identity |
| Skills (SKILL.md files) | Yes | Next message / worker spawn sees new skills |
| Bindings | Yes | Next message routes using new bindings |
Expand Down Expand Up @@ -489,6 +510,138 @@ Map of model names to ordered fallback chains. Used when the primary model retur
"anthropic/claude-sonnet-4-20250514" = ["anthropic/claude-haiku-4.5-20250514"]
```

### `[defaults.rig_alignment]`

Controls how Spacebot forwards Rig request semantics (`tool_choice`, `output_schema`) to providers, and whether channel/cortex streaming plus read-only worker tool concurrency are enabled.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `request_semantics_mode` | string | `"shadow"` | `off`: never forward `tool_choice`/`output_schema`; `shadow`: apply only when supported; `enforced`: fail unsupported requests explicitly |
| `channel_streaming` | bool | false | Enables channel `stream_prompt(...)` execution and adapter placeholder streaming |
| `cortex_chat_streaming` | bool | false | Enables cortex-chat `stream_prompt(...)` and SSE `text_delta` events |
| `worker_read_only_tool_concurrency` | integer | 1 | Requested per-request tool concurrency for workers (clamped to `worker_max_tool_concurrency`; standard workers still stay sequential unless their exposed surface is fully read-only) |
| `worker_max_tool_concurrency` | integer | 4 | Hard upper bound for worker request-level tool concurrency |
| `worker_read_only_tool_allowlist` | string[] | read-only defaults | Tools that are eligible for concurrent execution; any non-allowlisted exposed tool forces sequential execution |
| `output_schema_provider_allowlist` | string[] | `["openai","openai-chatgpt","anthropic"]` | Provider IDs allowed to receive structured output schema fields |
| `tool_choice_provider_allowlist` | string[] | `["openai","openai-chatgpt","anthropic"]` | Provider IDs allowed to receive tool-choice fields |

`request_semantics_mode` behavior:

- `off`: disables forwarding of both request semantics fields.
- `shadow`: evaluates support per provider and request shape, applies only supported fields, and logs the decision.
- `enforced`: same evaluation, but unsupported requested fields return an explicit provider error.

Worker concurrency safety behavior:

- Concurrency remains sequential unless **all** exposed worker tools for that request are allowlisted.
- If the configured allowlist itself contains mutable tools, Spacebot warns and forces sequential execution.
- The default worker surface still exposes mutating tools such as `shell`, `file`, `exec`, `task_update`, and `set_status`, so `worker_read_only_tool_concurrency > 1` only takes effect for specialized worker surfaces that expose solely allowlisted read-only tools.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Rig-alignment observability:

- `spacebot_rig_request_semantics_total{process,provider,field,decision}`
- `spacebot_rig_stream_sessions_total{process,provider,mode}`
- `spacebot_rig_time_to_first_delta_ms{process,provider}`
- `spacebot_rig_tool_concurrency_total{worker_type,concurrency}`
- `spacebot_rig_drift_detected_total{surface}`

## Rig Alignment Rollout Runbook

Use config-first rollout and rollback. Do not enable multiple new knobs at once.

### Stage 1: Semantics Shadow Mode

- Enable:
`request_semantics_mode = "shadow"`
- Evidence:
`cargo test forwards_ -- --nocapture`
`cargo test rejects_unsupported_semantics_enforced -- --nocapture`
`rg "request semantic decision" logs debug.log -g '*.log'`
- Rollback trigger:
supported providers start logging `decision=shadowed` for requests that should apply cleanly, or provider errors increase unexpectedly.
- Rollback:
`request_semantics_mode = "off"`

### Stage 2: Semantics Enforced In Staging

- Enable:
`request_semantics_mode = "enforced"`
- Evidence:
`cargo test rig_parity -- --nocapture`
`rg "request semantic decision|request semantics rejected" logs debug.log -g '*.log'`
- Rollback trigger:
staging traffic hits explicit compatibility errors for expected production routes.
- Rollback:
`request_semantics_mode = "shadow"`

### Stage 3: Channel Streaming Canary

- Enable:
`channel_streaming = true`
- Evidence:
`cargo test channel_streaming_reply_terminal -- --nocapture`
`cargo test adapter_streaming_throttle -- --nocapture`
`rg "stream_mode|time_to_first_delta_ms|terminal_reason" logs debug.log -g '*.log'`
- Rollback trigger:
missing final replies, placeholder cleanup failures, or adapter edit throttling regressions.
- Rollback:
`channel_streaming = false`

### Stage 4: Cortex Chat Streaming Canary

- Enable:
`cortex_chat_streaming = true`
- Evidence:
`cargo test cortex_chat_streaming_persists_final -- --nocapture`
`rg "stream_mode|time_to_first_delta_ms" logs debug.log -g '*.log'`
- Rollback trigger:
partial SSE events stop arriving or final assistant responses stop persisting.
- Rollback:
`cortex_chat_streaming = false`

### Stage 5: Worker Concurrency Canary

- Enable:
`worker_read_only_tool_concurrency = 2`
Keep `worker_max_tool_concurrency = 4` unless a smaller cap is required.
- Evidence:
`cargo test worker_concurrency_allowlist -- --nocapture`
`cargo test worker_concurrency_reduces_wall_clock -- --nocapture`
`rg "worker tool concurrency resolved|forcing sequential execution" logs debug.log -g '*.log'`
- Rollback trigger:
any mutable or mixed tool surface reaches concurrency > 1, or tool error rates regress during canary.
- Rollback:
`worker_read_only_tool_concurrency = 1`

## Rig Upgrade Checklist

Run this parity suite on every Rig upgrade before merge:

```bash
cargo test forwards_ -- --nocapture
cargo test rejects_unsupported_semantics_enforced -- --nocapture
cargo test channel_streaming_reply_terminal -- --nocapture
cargo test cortex_chat_streaming_persists_final -- --nocapture
cargo test adapter_streaming_throttle -- --nocapture
cargo test worker_concurrency_allowlist -- --nocapture
cargo test worker_concurrency_reduces_wall_clock -- --nocapture
cargo test rig_parity -- --nocapture
just preflight
just gate-pr
```

```toml
[defaults.rig_alignment]
request_semantics_mode = "enforced"
channel_streaming = true
cortex_chat_streaming = true
worker_read_only_tool_concurrency = 3
worker_max_tool_concurrency = 4
worker_read_only_tool_allowlist = ["read_skill", "spacebot_docs", "web_search"]
output_schema_provider_allowlist = ["openai", "openai-chatgpt", "anthropic"]
tool_choice_provider_allowlist = ["openai", "openai-chatgpt", "anthropic"]
```

### `[defaults.compaction]`

| Key | Type | Default | Description |
Expand Down
10 changes: 10 additions & 0 deletions interface/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export interface OutboundMessageDeltaEvent {
aggregated_text: string;
}

export interface OutboundStreamEndEvent {
type: "outbound_stream_end";
agent_id: string;
channel_id: string;
}

export interface TypingStateEvent {
type: "typing_state";
agent_id: string;
Expand Down Expand Up @@ -150,6 +156,7 @@ export type ApiEvent =
| InboundMessageEvent
| OutboundMessageEvent
| OutboundMessageDeltaEvent
| OutboundStreamEndEvent
| TypingStateEvent
| WorkerStartedEvent
| WorkerStatusEvent
Expand Down Expand Up @@ -535,6 +542,9 @@ export interface CortexChatMessagesResponse {

export type CortexChatSSEEvent =
| { type: "thinking" }
| { type: "text_delta"; text_delta: string }
| { type: "tool_started"; tool: string }
| { type: "tool_completed"; tool: string; result_preview: string }
| { type: "done"; full_text: string }
| { type: "error"; message: string };

Expand Down
Loading
Loading