diff --git a/docs/agent-tools/infrastructure.mdx b/docs/agent-tools/infrastructure.mdx
index cc1731b06..ef6c091fe 100644
--- a/docs/agent-tools/infrastructure.mdx
+++ b/docs/agent-tools/infrastructure.mdx
@@ -119,11 +119,23 @@ The `gateway` tool is the Swiss Army knife for system management, with **11 acti
|-----------|------|----------|-------------|
| `action` | string | Yes | `"env_set"` |
| `env_key` | string | Yes | Environment variable / secret name (uppercase, e.g., `OPENAI_API_KEY`) |
- | `env_value` | string | Yes | Secret value to store. Write-only: cannot be read back. |
+ | `env_value` | string | On the first call | Secret value to store. Write-only: cannot be read back. Omit on the confirm leg (see below). |
+ | `pending_action_id` | string | On the confirm leg | The id returned by the gated first call. Pass it with `_confirmed: true` **instead of** re-sending `env_value`. |
Setting environment variables requires security approval and user confirmation. The stored value is write-only and cannot be retrieved. Reference it in config as `${VAR_NAME}` (e.g., `GEMINI_API_KEY: ${GEMINI_API_KEY}`). Never write raw API keys into config files.
+
+ **The two-call approval flow never re-sends the secret.** The gated first call
+ stashes the value server-side (5-minute TTL, one-shot) and returns a
+ `pending_action_id`; after the user approves, the confirm call passes
+ `{ _confirmed: true, pending_action_id }` and the daemon replays the stashed
+ value. This matters because session redaction rewrites the secret in
+ conversation context between the two calls — re-sending `env_value` on the
+ confirm leg risks transmitting a redaction placeholder (rejected), and asking
+ the user to re-paste a credential into the chat is never acceptable. An
+ expired or already-consumed id returns `pending_action_expired`; re-initiate,
+ or have the operator run `comis secrets set ` on the host.
diff --git a/docs/agent-tools/messaging.mdx b/docs/agent-tools/messaging.mdx
index 60cb52b7f..6fab9604a 100644
--- a/docs/agent-tools/messaging.mdx
+++ b/docs/agent-tools/messaging.mdx
@@ -22,6 +22,13 @@ crossing account, thread, or conversation boundaries.
The `message` tool supports **7 actions** -- `send`, `reply`, `react`, `edit`, `delete`, `fetch`, and `attach`. Comis verifies the requested coordinates against the turn's authoritative endpoint, then dispatches to its exact channel adapter instance. Not every action is supported on every platform (see the [capability matrix](#per-channel-capability-matrix) below); the tool returns a structured error if you ask for something the channel cannot do.
+
+The turn's final assistant text is delivered automatically. After `message`
+already sends or replies with the user-facing content, the agent should finish
+with `NO_REPLY`; restating the same content as the final response sends a second
+message. `NO_REPLY` suppresses only that automatic final delivery.
+
+
All actions require the `channel_type` parameter to specify which platform adapter handles the request. This is a required string identifying the channel type (e.g., `telegram`, `discord`, `slack`, `whatsapp`, `signal`, `imessage`, `line`, `irc`, `email`).
### Quick Reference
diff --git a/docs/agent-tools/sessions.mdx b/docs/agent-tools/sessions.mdx
index 125b51a3f..d203dc57e 100644
--- a/docs/agent-tools/sessions.mdx
+++ b/docs/agent-tools/sessions.mdx
@@ -151,7 +151,7 @@ The four session tools (`session_search`, `session_status`, `sessions_history`,
| `async` | boolean | No | Accepted for explicit asynchronous intent; every spawn runs in the background and returns `runId` immediately. |
| `model` | string | No | Model override for the sub-agent |
| `artifact_refs` | string[] | No | File paths for the sub-agent to reference in its workspace |
- | `tool_groups` | string[] | No | Tool group names for sub-agent tool filtering (e.g., `"coding"`, `"web"`) |
+ | `tool_groups` | string[] | No | Tool group names for sub-agent tool filtering (e.g., `"coding"`, `"web"`). The default child profile excludes MCP tools and `message`; use `"full"` when the task genuinely requires those capabilities. |
| `required_tools` | string[] | No | Tool names that must be reachable; spawn fails before execution if the child profile cannot reach one. |
| `objective` | string | No | Objective statement that survives context compaction |
| `include_parent_history` | `"none"` or `"summary"` | No | Include a condensed summary of the parent conversation (default: `"none"`) |
@@ -160,6 +160,8 @@ The four session tools (`session_search`, `session_status`, `sessions_history`,
| `max_steps` | integer | No | Maximum execution steps (floor of 30, default 50, capped at config default) |
`sessions_spawn` never blocks for the child result. It returns the run ID and a background-running marker immediately; a queued spawn additionally returns `queued: true`. The result is delivered to the immutable originating conversation, so the model-facing tool cannot select an announcement channel. Use `subagents` with `action: wait` to await owned children without polling.
+ **When the agent reaches for it.** An agent that has `sessions_spawn` on its surface receives a concise delegation directive in its initial system prompt: delegate rather than work inline whenever a task needs more than ~30 seconds of tool time, generates media, writes three or more files, requires deep research, or runs four or more dependent steps. Independent subtasks are spawned as multiple calls in one response and run in parallel. The fuller guide (goal-oriented task phrasing, child tool-profile guidance, and what not to delegate) follows the first successful tool result and is delivered only once.
+
Sub-agent results are automatically condensed (if over the token threshold) and formatted with metadata tags before being returned to the parent. See [Subagent Context Lifecycle](/agents/subagent-lifecycle) for the full pipeline.
**Example -- Spawn a coding agent:**
diff --git a/docs/agents/compaction.mdx b/docs/agents/compaction.mdx
index be3a2530d..d6b20b075 100644
--- a/docs/agents/compaction.mdx
+++ b/docs/agents/compaction.mdx
@@ -361,9 +361,11 @@ A three-tier escalation ensures summaries always fit within their target size:
- **Truncation** -- A bounded count-only note (marker-prefixed, guaranteed to
fit and to shrink below the source it replaces).
-The most recent turns (the "fresh tail", default 8 turns) are always protected
-from compaction, ensuring your agent has full, uncompressed access to the latest
-exchanges.
+The "fresh tail" starts with a configured floor of recent steps plus every
+message newer than the store's persisted horizon. If residual token bounding is
+needed, completed tool segments may be trimmed, but the current turn's
+originating request stays pinned. See the [`freshTailTurns` config contract](/reference/config-yaml#context-engine-agents-contextengine)
+for the small-window clamp and residual-token rules.
One size guard applies inside the protected fresh tail: a single message whose
text alone approaches the model's effective context window (an oversized tool
@@ -491,7 +493,7 @@ change anything.
| `contextEngine.observationDeactivationChars` | number | `80000` | Character threshold to deactivate observation masking (20K-500K) |
| `contextEngine.ephemeralKeepWindow` | number | `10` | Recent ephemeral tool results to preserve from masking (1-50) |
| `contextEngine.contextThreshold` | number | `0.75` | **DAG mode:** context-utilization fraction (of the turn's effective budget window — the reconciled context window under any capability-class cap) that triggers a leaf-summarization pass at the end of a turn (0.1-0.95) |
-| `contextEngine.freshTailTurns` | number | `8` | **DAG mode:** most-recent steps (assistant + tool round-trips) always kept verbatim and never summarized/evicted (1-50) |
+| `contextEngine.freshTailTurns` | number | `8` | **DAG mode:** configured step floor for the verbatim tail (1-50). See the [config reference](/reference/config-yaml#context-engine-agents-contextengine) for the effective-tail, persisted-horizon, and residual-token rules. |
| `contextEngine.leafChunkTokens` | number | `20000` | **DAG mode:** token cap for the oldest out-of-tail chunk summarized into one leaf summary (1K-100K); clamped at runtime to the resolved summarizer model's window — the smaller of its configured window and the probed served window when the summarizer runs on the served-bound provider — minus the summary target, template overhead, and previous-summary size, so a small compaction summarizer or a served-bound primary is never fed an over-window chunk; a single message larger than the clamped cap is replaced by a bounded deterministic extraction (no LLM call) |
| `contextEngine.leafTargetTokens` | number | `1200` | **DAG mode:** target token size for a leaf summary (96-5000) |
| `contextEngine.deferCompaction` | boolean | `true` | **DAG mode:** run the afterTurn leaf + condense passes in the background on the per-conversation serializer (never blocking the turn). `false` runs them inline (deterministic, for tests) |
diff --git a/docs/developer-guide/event-bus.mdx b/docs/developer-guide/event-bus.mdx
index e5b41fb85..2b855b33d 100644
--- a/docs/developer-guide/event-bus.mdx
+++ b/docs/developer-guide/event-bus.mdx
@@ -63,11 +63,11 @@ A quick reference for the events developers are most likely to need when buildin
| `plugin:registered` | Infra | Plugin loaded | Plugin lifecycle tracking |
| `diagnostic:message_processed` | Infra | Full message lifecycle complete | End-to-end metrics |
-## Complete Event Reference
+## Event Reference
-
+
Message lifecycle, session management, subagent lifecycle, compaction, context engine lifecycle, context engine metrics, response filtering, execution control, and dead-letter queue events.
@@ -80,6 +80,7 @@ Message lifecycle, session management, subagent lifecycle, compaction, context e
| `compaction:flush` | sessionKey, memoriesWritten, trigger, success, timestamp |
| `context:masked` | agentId, sessionKey, maskedCount, totalChars, persistedToDisk, timestamp |
| `context:compacted` | agentId, sessionKey, fallbackLevel, attempts, originalMessages, keptMessages, timestamp |
+| `context:budget_computed` | agentId, sessionKey, windowTokens, rawContextWindowTokens, windowCapSource, systemTokens, freshTailTokens, budgetedHistoryTokens, keptCount, assembledInputTokens, outputHeadroom, verdict, freshTailSteps, freshTailStepsConfigured, originatingRequestRetained, freshTailTrimmedCount |
| `context:rehydrated` | agentId, sessionKey, sectionsInjected, filesInjected, skillsInjected, overflowStripped, timestamp |
| `context:overflow` | agentId, sessionKey, contextTokens, budgetTokens, recoveryAction, timestamp |
| `context:evicted` | agentId, sessionKey, evictedCount, evictedChars, categories, timestamp |
@@ -124,7 +125,7 @@ See [Compaction](/agents/compaction) for details on what triggers each event.
-
+
Skill lifecycle, tool execution, audit logging, observability (tokens and latency), cache break detection, model failover, security, execution graph, per-node sub-agent budget breaches, pipeline authoring telemetry, small-model graph authoring (repair + intent synthesis), provider health, SEP (step-execution planning), and exec command blocking events.
@@ -171,7 +172,7 @@ Skill lifecycle, tool execution, audit logging, observability (tokens and latenc
| `skill:rejected` | skillName, reason, violations, timestamp |
| `skill:updated` | skillName, scope, agentId, timestamp |
| `skills:reloaded` | agentId, skillCount, timestamp |
-| `tool:executed` | toolName, durationMs, success, timestamp, userId, traceId, agentId, sessionKey, params, errorMessage, errorKind, description, truncated, fullChars, returnedChars |
+| `tool:executed` | toolName, toolCallId, durationMs, success, backgrounded, timestamp, userId, traceId, agentId, sessionKey, params, errorMessage, errorKind, description, truncated, fullChars, returnedChars |
| `tool:policy_filtered` | profile, agentId, filtered, timestamp |
| `tool:started` | toolName, toolCallId, timestamp, agentId, sessionKey, traceId, description |
@@ -218,7 +219,7 @@ See [Resilience: Self-healing delivery retries](/agents/resilience#self-healing-
-
+
Channel registration, sender blocking, command queuing, streaming delivery, typing indicators, auto-reply, send policies, debounce, group history, follow-up chains, priority lanes, elevated routing, retry engine, ack reactions, inbound reaction capture, steer lifecycle, block coalescing, unified delivery pipeline, delivery queue, channel health monitoring, delivery hooks, and sub-agent proxy typing events.
@@ -286,8 +287,8 @@ Approval gates, config patches, plugin lifecycle, hook execution, auth token rot
| `approval:resolved` | requestId, approved, approvedBy, reason, resolvedAt |
| `auth:token_rotated` | provider, profileName, expiresAtMs, timestamp |
| `background_task:cancelled` | agentId, taskId, toolName, timestamp |
-| `background_task:completed` | agentId, taskId, toolName, durationMs, origin, timestamp |
-| `background_task:failed` | agentId, taskId, toolName, error, durationMs, origin, timestamp |
+| `background_task:completed` | agentId, taskId, toolName, durationMs, origin, dispatchRedelivery, toolCallId, sessionKey, traceId, timestamp |
+| `background_task:failed` | agentId, taskId, toolName, error, errorKind, durationMs, origin, dispatchRedelivery, toolCallId, sessionKey, traceId, timestamp |
| `background_task:notified` | agentId, taskId, toolName, sessionKey, notified, reason, traceId, timestamp, trajectoryRecorded |
| `background_task:promoted` | agentId, taskId, toolName, timestamp |
| `background_task:reentered` | taskId, agentId, sessionKey, hopCount, traceId, timestamp |
@@ -328,7 +329,7 @@ Approval gates, config patches, plugin lifecycle, hook execution, auth token rot
| `scheduler:heartbeat_wake_terminal` | correlationId, target, lane, retainedReason, status, cancellationReason, eventEntryCount, durationMs, errorKind, timestamp |
| `scheduler:job_suspended` | jobId, jobName, agentId, consecutiveErrors, lastError, timestamp, deliveryTarget |
| `scheduler:wake_gate` | jobId, agentId, wake, durationMs, toolCalls, estTurnsSaved, failedOpen, timestamp |
-| `secret:accessed` | secretName, agentId, outcome, timestamp |
+| `secret:accessed` | secretName, agentId, outcome, sessionKey, traceId, timestamp |
| `secret:modified` | secretName, action, timestamp |
| `security:warn` | category, agentId, message, timestamp |
| `system:error` | error, source |
diff --git a/docs/developer-guide/generic-agent-architecture.md b/docs/developer-guide/generic-agent-architecture.md
index 712fafd59..c7437cc0b 100644
--- a/docs/developer-guide/generic-agent-architecture.md
+++ b/docs/developer-guide/generic-agent-architecture.md
@@ -46,7 +46,7 @@ Execution state such as sender trust, locale policy, selected skills, and compac
`ResponseLocalePolicy` is an open, strict domain type. Explicit locale tags are canonicalized with `Intl.getCanonicalLocales`; locale is not a closed language union. Unicode script analysis may support search, token estimation, bidirectional safety, and diagnostics, but it does not coerce response language.
-Translation target is separate from surrounding response locale. A validated request or operator locale enables post-generation script enforcement. When no locale metadata exists, a non-Latin current request may produce an open `und-