Clapilot-Agent Request Execution Flows

Detailed native request routing, execution backend mapping, and prompt-assembly flow for web chat and external channels.

This page documents the active native clapilot-agent request path, not the old compatibility runtime.

It focuses on two questions:

  1. How a web chat turn or channel message becomes a concrete provider request.
  2. How the final model input is assembled from system prompts, memory, tools, history, and current input.

Primary implementation sources:

  • src/app/api/chat/route.ts
  • src/app/api/agent-runtime/channels/[channel]/inbound/route.ts
  • services/clapilot-agent/src/channels/index.mjs
  • services/clapilot-agent/src/sessions/index.mjs
  • services/clapilot-agent/src/providers/index.mjs
  • services/clapilot-agent/src/memory/index.mjs

Flow 1: Request routing, model selection, bridge selection, provider call

Native request routingBoth web chat and approved external channels converge into the same native session engine before provider execution.Ingress into ClapilotWeb chat/api/chatSession write, UI context,attachments, RAG, modeloverride patchingChannel ingressTelegram / Slack / WhatsAppNext.js forwards webhook to/internal/channels/:channel/inboundinside clapilot-agentThread and approval gateDedup event idResolve channel thread keyCheck approval / linked userResolve channel default modelNative run entrysessions.executeRun()Unified path for web chat,channels, jobs andAPI-triggered runsSelection and executionModel selectionproviders.resolveProviderSelection()1. explicit session/request model2. global native_model_routing.priority3. provider default modelExecution adapteropenai / azure / compatible- direct HTTP OpenAI-like pathanthropic- Messages API or Claude CLI bridgeBridge decisionsOpenAI Codex auth mode- Codex bridgeAnthropic setup-token- Claude CLI stream-json bridgeProvider request/v1/chat/completions/v1/responsesAnthropic MessagesBedrock Converse / CLIFallbacks: if the chosen model fails, the runtime retries through the remaining global priority entries or the provider row's fallback_order.

What actually happens

  1. Web chat starts in src/app/api/chat/route.ts. The route normalizes attachments, page context, document references, RAG hints, and the session model override, then forwards the run to native clapilot-agent.
  2. Channel traffic starts in src/app/api/agent-runtime/channels/[channel]/inbound/route.ts, which forwards the raw inbound webhook to /internal/channels/:channel/inbound.
  3. Inside services/clapilot-agent/src/channels/index.mjs, the runtime parses the provider-specific payload, deduplicates by external event id, resolves either the fallback channel:<channel>:<threadKey> session or the mapped canonical web/team-chat session key from the approval metadata, checks approval state, performs channel-specific normalization such as Telegram voice-note transcription, and decides whether the thread stays on the standard native path or escalates into the coding/orchestrator path.
  4. Both paths that stay in the standard native agent loop end up in sessions.executeRun() inside services/clapilot-agent/src/sessions/index.mjs.
  5. executeRun() resolves the base model from the explicit run payload, stored session override, or the global native_model_routing.priority list through providers.resolveProviderSelection(). When adaptive routing is enabled, the native policy layer evaluates only the configured priority candidates before final provider resolution. Shadow mode records its recommendation without changing execution; apply mode can change the model and Clapilot-code profile. Explicit model/profile choices, specialized agents, Mixture-of-Agents presets, public-channel bridge restrictions, and active tool-loop continuity remain protected.
  6. resolveProviderSelection() in services/clapilot-agent/src/providers/index.mjs does not just pick a model name. It also resolves the provider row, the effective provider/model ref, context-window and output-token limits, and the fallback chain.
  7. sessions.executeRun() then chooses the runtime core plus the execution adapter:
    • non-specialized providers use either Clapilot-code (Assistant) (native) or Clapilot-code (Coding) (embedded_pi) depending on the explicit run override, the selected provider's metadata.agent_core, and native_model_routing.non_specialized_agent_core, in that order
    • OpenAI, Azure OpenAI, and OpenAI-compatible rows use the OpenAI-like HTTP adapter.
    • Rich multimodal payloads prefer the Responses-style path.
    • Anthropic API-key rows use the Anthropic Messages API.
    • Anthropic setup-token rows use the Claude CLI bridge.
    • OpenAI rows in Codex auth mode use the Codex bridge.
    • Bedrock rows use the Converse adapter.
  8. Every upstream attempt is written into agent_model_request_logs, so one native run can still produce multiple provider requests when fallback or tool loops happen.

OpenAI and Azure OpenAI streaming chat-completions requests explicitly request the terminal usage chunk. OpenAI-family request log metadata includes toolPhase, toolLoopStep, previousToolDurationMs, and the applied provider deadline, while prompt_layer_tokens_json.provider_input_estimate supplies the prompt-size dimension. Streaming requests use a 75-second time-to-first-response/idle deadline that refreshes on data or heartbeat activity; non-streaming requests use a separate five-minute wall-clock cap. Before visible output, expiry enters the normal configured fallback path instead of leaving the chat pending for minutes. 9. Provider timeout retries use bounded exponential backoff with positive decorrelated jitter: the capped exponential delay receives up to another 50 percent random delay. OpenAI-family adapters do not stack their older transient retry loop on top of timeout retries. The process-local circuit breaker counts one failure only after a complete provider run exhausts its retries, then skips that provider/model temporarily after repeated failed runs. 10. Subscription/session quota exhaustion is classified separately as quota_exhausted. Before any visible delta or tool side effect, the runtime cools down that model until the provider reset (or a conservative 15-minute default) and immediately tries the next approved configured fallback. If every approved model is quota-blocked, scheduled work retains its original job row and is rescheduled with exponential jittered backoff, never dead-lettered merely because its ordinary retry limit was reached. 11. Retry and fallback replay is allowed only before irreversible progress. Once the runtime has emitted a delta, emitted a tool event, or invoked a tool, any later provider failure aborts that run instead of replaying the original messages and potentially duplicating output or side effects.

Backend matrix: which provider uses which engine

Execution Backends
Provider row does not automatically mean bridge
Most configured providers stay on native direct HTTP adapters. Bridge-backed execution only appears for specific auth modes such as Codex OAuth or Claude setup-token / OAuth execution.
Direct adapters and bridges are both first-class native runtime paths
Direct HTTP
OpenAI
Auth: API key
Adapter: native OpenAI family adapter
Endpoint: /responses for rich content, otherwise /chat/completions
Transport: http
Bridge
OpenAI Codex
Auth: codex_oauth
Adapter: runCodexSubscriptionBridge() in sessions
Transport: orchestrator_bridge
Under the hood: creates an orchestrator session with provider: "codex", not the generic OpenAI HTTP adapter
Direct HTTP
Anthropic API
Auth: API key
Adapter: native Anthropic adapter
Endpoint: /v1/messages
Transport: http
CLI Bridge
Claude Subscription
Auth: Anthropic oauth_token / setup-token flow
Adapter: runClaudeSubscriptionBridge()
Transport: cli_bridge
Under the hood: local claude CLI with --output-format stream-json and MCP config injection
Direct HTTP
Azure OpenAI
Auth: Azure key
Adapter: native OpenAI-like adapter
Endpoint: Azure /openai/v1 paths
Transport: http
Direct HTTP
OpenAI Compatible
Auth: optional API key
Adapter: native OpenAI-like adapter
Endpoint: /chat/completions
Transport: http
Direct HTTP
AWS Bedrock
Auth: Bedrock bearer token / provider secret
Adapter: native Bedrock adapter
Endpoint: /model/<model>/converse
Transport: http

What is actually under the hood

  • openai with a normal API key does not go through PI or the Codex bridge. It stays on the native OpenAI-family HTTP adapter.
  • azure_openai, openai_compatible, ollama, aws_bedrock, and anthropic with an API key also stay on direct native provider adapters. Ollama uses its native chat/embedding API rather than the OpenAI-compatible adapter.
  • anthropic with oauth_token is not PI-backed. It switches into the Claude CLI bridge and runs through the local claude binary.
  • Claude subscription chat is now resumable per Clapilot session: Clapilot stores a hidden Claude bridge session id and follow-up turns reuse that same Claude Code conversation instead of launching a fully stateless CLI run.
  • openai with codex_oauth is the main path that switches away from the normal native HTTP adapter. That path goes through runCodexSubscriptionBridge(), which now keeps a hidden Codex app-server thread per Clapilot session and sends follow-up turns into that same external thread.
  • Codex bridge steering is direct: queued web-chat follow-ups can call POST /api/chat/steer, which resolves the hidden :subscription-bridge:codex session and forwards the message through Codex app-server turn/steer.
  • Claude bridge steering is direct while the CLI bridge process is alive: Clapilot starts the bridge with --input-format stream-json, keeps stdin open for the active run, and writes queued follow-ups as realtime user-message events.
  • Normal native/embedded-PI runs also have live steering while the run is active: POST /api/chat/steer queues a LIVE USER STEER notice. An active tool receives it in that tool result; if the provider is generating a final answer without a tool call, the runtime aborts that request, keeps the same stream open, and immediately starts a continuation provider step. Any text already emitted and the continuation text are preserved in order.
  • Native steering is accepted only while a provider or tool step guarantees a consumer. A request landing in the short transition/finalization gap returns reason=native_boundary_transition; the UI keeps it queued and the normal dispatcher starts it after the current run, preventing an acknowledged follow-up from being dropped.
  • If the agent runtime restarted during a chat run, the replacement process has no in-memory steering channel for that dead run. The steer endpoint checks the latest persisted run and verifies that its idempotency key still owns the pending chat placeholder: matching terminal runs return retryAsNewTurn=true, the web route immediately reconciles the stale placeholder, and the client restarts the queued message as a normal new turn. A persisted queued/running row without a live channel is not duplicated; it remains queued with reason=run_not_steerable. An older terminal run without the pending placeholder also stays non-recoverable so it cannot race a newer turn still in preflight.
  • The web UI shows the queued-message direct-send button only for model entries marked supportsSteering by /api/chat/models: Codex and Claude subscription rows are always marked, and non-specialized rows are marked when their active profile uses the embedded_pi wire value.
  • Clapilot-code (Coding) (embedded_pi) is the provider-agnostic coding-agent profile for providers that do not already require the specialized Codex or Claude subscription runtimes.
  • Clapilot-code (Coding) does not swap the model backend to Codex. It keeps the selected provider/model transport and changes the session-layer profile, prompt contract, tool profile, and tool-loop budget.
  • The embedded_pi wire path has no external harness-level interrupt primitive. Clapilot therefore owns interruption itself: follow-ups are delivered at the current tool-result boundary, or the runtime aborts the in-flight provider HTTP request and starts the continuation inside the same native run.
  • The older embedded_pi bookkeeping markers around memory flush / compaction remain internal compatibility identifiers; the user-facing profile name is Clapilot-code (Coding).
  • Adaptive route decisions and their terminal technical outcomes are persisted in agent_adaptive_route_decisions and linked one-to-one to agent_runs. Provider retries and fallback still belong to the existing provider layer after the initial route decision.
  • There is also a separate channel-orchestrator escalation path for coding-style work in services/clapilot-agent/src/channels/index.mjs, but that is not the default execution backend for ordinary provider rows.

Clapilot-code robustness

The shared native provider loop hardens Clapilot-code (Assistant) (native) and Clapilot-code (Coding) (embedded_pi) against malformed output from weak, open, and local models. The normalization layer runs after each provider response is decoded and before assistant history is appended or tools are dispatched. It is shared by OpenAI Responses, OpenAI-compatible Chat Completions, Anthropic Messages, Bedrock Converse, and Gemini GenerateContent.

  • closed reasoning blocks such as <think>...</think> are removed; an unterminated reasoning block is removed only when its opening tag occurs at a block boundary, so ordinary prose that mentions a tag is preserved. Repeated orphan closing markers from local model templates are treated as a malformed reasoning boundary: intermediate content is removed while the trailing final answer is preserved
  • tool calls emitted in text through <tool_call>, <tool_calls>, <function_call>, or boundary-gated Gemma <function name="..."> blocks are salvaged when no structured tool call is present
  • malformed tool-argument JSON receives conservative repairs for invalid string control characters, trailing junk, and one unambiguous missing string/container close
  • lone UTF-16 surrogate code points are removed before text reaches JSON, history, or database paths
  • structured tool calls always take precedence over text salvage, preventing double execution; salvaged and repaired calls still pass through the existing repeat guard and tool-step budget

The strong-model invariant is losslessness: clean assistant text with valid structured tool calls and no relevant tag markers takes an early fast path. The original text and structured tool-call array pass through unchanged; normalization only attempts to recover output that is already malformed or encoded in the wrong channel.

Tool schemas are normalized per provider without weakening the default path for strong providers. Gemini always receives its restricted supported schema subset, including const to enum conversion and conservative combinator flattening. Other providers default to metadata.tool_schema_compat: "off"; a local or grammar-constrained backend can opt into "strict" to remove schema keywords that commonly break llama.cpp and JSON-schema-to-grammar converters. The setting applies at every native transport tool mapper: OpenAI Chat Completions, OpenAI Responses (including xAI), Ollama Chat, Anthropic Messages, Bedrock Converse, and Gemini GenerateContent.

Provider failures also pass through a shared classifier with stable reason codes such as auth, billing, rate_limit, quota_exhausted, overloaded, timeout, context_overflow, model_not_found, and content_policy_blocked. A compress_context action now gets one same-provider retry when the provider-neutral message history has safe older complete turns to remove. The retry preserves all system messages, the final user turn, and the most recent complete turns. It is guarded once per request and is forbidden after any delta, tool event, or tool execution; if compression cannot help or the compressed attempt fails, the existing configured fallback/abort path continues unchanged. Ordinary rate limits enter the stepped cooldown. Quota exhaustion uses provider reset metadata when available, tries only configured approved fallbacks, and otherwise keeps automation work queued. Quota retries do not publish transient failure notifications, preventing Teamchat storms; the eventual successful run uses the existing claimed-job and delivery guards and therefore delivers once. Interactive chat receives a localized neutral status instead of upstream provider text. Metrics expose providerQuotaLimited, providerQuotaResetKnown, and providerQuotaFallbacks in the runtime snapshot.

Entry-point differences worth knowing

  • Web chat uses /internal/runs for the normal streaming path when the native runtime client is active.
  • Web chat uses /internal/responses when the current request contains image/file parts that must be sent as multimodal content.
  • The lower-level /internal/chat/completions and /internal/responses handlers both still end in sessions.executeRun().
  • Specialized chat agents still enter through the same native execution endpoints, but they now pass an explicit specializedAgent execution profile into sessions.executeRun() instead of pretending to be the default agent:main path.
  • Direct @agentHandle mentions and delegate_to_specialized_agent no longer force the parent chat request to await the specialist. Clapilot persists a visible pending assistant bubble, writes one row into specialized_agent_tasks, and lets a detached app-side worker run the specialist later.
  • Delegated specialist runs can now trigger a second asynchronous main-agent run after the specialist finishes, so the original main chat can continue with a later follow-up message instead of blocking the original turn.
  • Generic fan-out subagents use spawn_clapilot_subagents instead of temporary specialized-agent records. Clapilot writes a clapilot_subagent_batches row plus one clapilot_subagent_tasks row per worker, streams each worker into a visible pending assistant bubble, then triggers one asynchronous main-agent callback when the whole batch has no queued/running workers left.
  • Telegram can stream native deltas directly while keeping the same visible reply message updated in place.
  • Channel model choice is layered: session override, then channel default model, then global default model.
  • External bridge follow-up state is now split cleanly:
    • embedded_pi and native keep continuity entirely inside Clapilot's native session state
    • Claude subscription keeps both the native Clapilot session state and a resumable hidden Claude CLI session id
    • Codex subscription keeps both the native Clapilot session state and a resumable hidden Codex thread managed through the orchestrator session broker

Flow 2: Input enrichment, prompt assembly, memory, tools, and final model payload

Prompt assembly and enrichmentThe provider never sees only the raw user text. Clapilot and clapilot-agent assemble a layered payload first.1. Current inputUser text or channel textOptional input_image / input_fileVisible chat transcript when theweb route inlines conversation state2. Clapilot-side enrichmentChat behavior promptCanvas/system prompt when neededClient route/module/page contextRAG and selected document context3. memory.bootstrap()Prompt files from workspaceSemantic recallSession graph summariesShared facts and workspace memory4. Tool routingfull / compact / ultraTool bundle chosen frominput + route + user accessMinimal context if ultraWhat sessions.executeRun() assembles before the provider callRuntime system promptTool rules, memory policy,workspace, docs, time, identityBootstrap prompt filesAGENTS.md, IDENTITY.md,USER.md, MEMORY.md, etc.Retrieved memoryVector + keyword recalland session-graph summariesStored historyRecent turns only,fitted to token budgetCurrent turnText plusrich content partsPre-send maintenance and final payloadMemory flushIf transcript or prompt budget is too large,write durable notes into memory/YYYY-MM-DD.mdSession compactionOlder turns become a persisted summaryand only the recent tail stays verbatimBudget fitDrop retrieved context before droppingrecent history when tokens get tightFinal provider payloadsystem layers + replayed history +current user content + tool schemas

Layer order in practice

The final prompt assembled inside sessions.executeRun() follows this order:

  1. Runtime-owned system prompt from buildNativeSystemPrompt(), buildEmbeddedPiSystemPrompt(), or their compact variants.
  2. Persisted compaction summary, if a previous safeguard compaction already exists.
  3. Bootstrap prompt-file block from memory.bootstrap().
  4. Retrieved memory context from native recall storage.
  5. Approved learning objects from the native learning ledger, capped as the separate approved_learning_objects prompt layer.
  6. Any extra system prompt passed in by the app or channel layer.
  7. Replayed recent session history from agent_runs, trimmed to fit the remaining budget.
  8. The current request message, including multimodal content parts when applicable.

Where each layer comes from

  • Clapilot-side enrichment is done in src/app/api/chat/route.ts.
    • It adds chat behavior rules, optional canvas instructions, route/module/page context, selected document references, and RAG context.
    • With attachments, it converts the latest turn into input_image / input_file parts.
  • Native memory bootstrap is done in services/clapilot-agent/src/memory/index.mjs.
    • memory.bootstrap() loads prompt files, syncs memory/**/*.md, retrieves approved canonical assertions through lexical/vector reciprocal-rank fusion plus compatible durable-memory hits, and appends session-graph summaries.
  • Approved learning retrieval is done by services/clapilot-agent/src/learning-objects/index.mjs and wired into sessions.executeRun().
    • Only approved, visible, unexpired objects are eligible.
    • Durable facts can become prompt context, hot snapshots stay scoped to their session/channel/entity lifetime, and procedure proposals are rendered only as approved hints rather than executable tools.
    • If the prompt is over budget, the runtime drops this layer before trimming stored conversation history and records learning.used_in_prompt audit events only for objects actually injected.
    • Agents can explicitly query the same approved/visible corpus with read-only learning_search and learning_get_object tools when the user asks what Clapilot has learned; candidates and rejected objects stay in the Learning settings/control-plane flow.
  • Unified run-time context retrieval is done in services/clapilot-agent/src/sessions/index.mjs.
    • context_search is the preferred first-pass tool for broad internal knowledge, Wiki synthesis, learned facts, memory, session summaries, and Knowledge Graph lookup.
    • context_get reads one source-qualified result (learning:<id>, wiki:<id-or-slug>, memory:<id>, history:<id>, knowledge_claim:<id>, or knowledge_entity:<id>) before the agent relies on it as evidence.
    • Source-specific memory_*, learning_*, wiki_*, and knowledge_* tools remain available for drill-down, graph traversal, and writes.
  • Native runtime prompt construction is done in services/clapilot-agent/src/sessions/index.mjs.
    • getNativeTools() decides the tool catalog from current text, UI context, user access, and the selected tool profile.
    • buildNativeSystemPrompt() and buildEmbeddedPiSystemPrompt() add runtime identity, tooling rules, workspace and docs guidance, current time, and session metadata.
    • specialist runs now bypass the normal broad page-context prompt path, use a compact runtime prompt builder, and apply a strict allowlist from specializedAgent.allowedToolNames.
    • specialist model routing is now layered: explicit runtime override, then specialized_agents.default_model_ref, then the current chat/session model, then the global provider default.
    • detached specialist executions are coordinated by src/lib/specialized-agent-tasks.ts; it claims queued tasks, runs the scoped specialist session, finalizes the visible specialist message, and optionally schedules a follow-up agent:main callback run with the specialist result. For Team Chat @agentHandle mentions, every explicitly mentioned invited specialist receives its own queued task and pending bubble in parallel. The worker adds only a compact visible room-context block of up to 10 relevant previous messages to each specialist request so specialists can understand the recent discussion without inheriting Angela's broader transcript replay.
    • When the resolved profile is Clapilot-code (Coding) (embedded_pi), non-specialized providers use the Coding prompt contract and a larger tool-loop budget while still using the same provider adapter.
  • Pre-send maintenance is also done in services/clapilot-agent/src/sessions/index.mjs.
    • maybeFlushSessionMemory() writes durable notes before the run when the transcript is getting too large.
    • maybeCompactSessionHistory() persists a structured session summary when older history needs to be compressed.
  • Post-response maintenance is detached from the provider response path.
    • runPostResponseMemoryMaintenance() stores summaries, records lossless replay messages, compacts the context graph, stores shared facts, and then asks the learning ledger to extract safe candidates from the same shared-facts output.
    • Learning extraction creates canonical durable facts or low-risk procedure drafts. Safe evidence-backed facts use the opt-out activation path immediately. The protected Learning Curator later uses its configured model to check only new or changed facts, keeping by default and rejecting only high-confidence false, unsafe, duplicate, non-durable, or clearly worthless content.

Important behavior details

  • lightContext keeps the run smaller, but still allows prompt files and session memory in a reduced form. Approved-learning prompt injection is skipped in this mode.
  • minimalContext is the stricter mode used for ultra-low-context models. It skips the normal bootstrap, retrieval, compaction-summary injection, approved-learning injection, and stored-history replay path.
  • Read-only Context and Learning tools remain discoverable through native tool routing, skills mode, compact tools, and specialist core allowlists; they are separate from automatic prompt injection and still enforce visibility and approval at execution time.
  • Conservative learning extraction is also skipped for lightContext, minimalContext, public embeds, specialized-agent runs, ultra tool-profile runs, and failed runs. Codex/Claude subscription-bridge runs are allowed by default because candidate creation reuses the already available shared-facts output; operators can set CLAPILOT_AGENT_LEARNING_EXTRACTION_ALLOW_SUBSCRIPTION_RUNTIME=false to disable that path. Safe durable facts activate optimistically, while procedure proposals, corrections, conflicts, weak evidence, and unsafe categories still require review. The asynchronous model-backed curator can later reject an active fact without overriding a manual decision.
  • Specialized agents are isolated by session key (agent:<specialistHandle>:openai-user:<chat-boundary>), so they replay only their own prior specialist turns for that chat boundary instead of the main agent's hidden session history. In Team Chat, prior specialist replies remain visible transcript messages, so later normal Angela turns can still use them through normal group history replay.
  • The runtime always chooses tools before the provider request, not after. Tool schemas are part of the actual provider payload.
  • The model can trigger multiple provider requests in one run because the provider loop may need tool-call follow-ups, retries, or fallbacks.

Token efficiency: prompt caching and tool catalog

The assembled prompt is dominated by the native tool schemas (~33–45k tokens if the full catalog — currently well over 200 tools — were attached wholesale) and the static system blocks (~5–6k). Three mechanisms keep the per-turn cost down:

  1. Provider prompt caching. The session runtime tags the stable prefix — runtime system prompt, persisted compaction summary, bootstrap prompt files, and stable app/UI system context — with cache: "stable". For text-only native turns it then builds a [# Trusted Current Turn Context] user message containing current time, retrieved memory, approved Learning context, optional Mixture-of-Agents synthesis, and the raw request. That exact augmented turn is stored as agent_runs.input_payload.promptCacheReplay and replayed before the next turn when its stable-prefix hash still matches. The resulting provider conversation is append-only instead of rebuilding volatile system blocks ahead of history on every request.

    • Anthropic: buildAnthropicSystemBlocks() in services/clapilot-agent/src/providers/index.mjs converts the system field into the array form and places a single cache_control: { type: "ephemeral" } breakpoint on the last block in the contiguous-from-start stable run. A second breakpoint is placed on the last entry of the tools array by buildAnthropicTools(..., { cache: true }). Reads cost ~10% of writes after the first turn.
    • OpenAI / Azure OpenAI: automatic caching activates on stable prefixes ≥1024 tokens; runOpenAiLike() adds prompt_cache_key (prefers payload.userId, falls back to sessionKey) to keep matching prefixes on the same cache shard for higher hit rate. openai_compatible (vLLM, llama.cpp/llama-server, LM Studio, local MiniMax, ...) is excluded by default because many local servers strict-validate the request body and reject unknown fields. Flip Settings → ClapilotAICore → Runtime → "prompt_cache_key fuer openai_compatible" to opt in once you've verified your local server tolerates and routes by it. First-class Ollama requests use the native Ollama adapter and never receive this OpenAI-only field.
    • Gemini: implicit caching is automatic on Gemini 2.5+ when the prefix is stable; no body changes are needed. Explicit cachedContent is a future workstream.
    • AWS Bedrock (Converse): buildBedrockSystemBlocks() appends a { cachePoint: { type: "default" } } marker after the last contiguous-from-start stable system block; buildBedrockTools(..., { cache: true }) appends the same marker after the last tool in toolConfig.tools. Supported on Anthropic Claude and Amazon Nova models served via Bedrock.
    • Clapilot-code (Coding) (agentCore = embedded_pi): uses the same payload.messages shape as Clapilot-code (Assistant) (native; buildEmbeddedPiSystemPrompt() wraps buildNativeSystemPrompt()), so the cache hint flows through unchanged and the underlying provider runner applies caching identically.
    • Claude CLI / Claude subscription bridge: the bridge does not place its own cache_control markers — the spawned claude CLI handles caching with Anthropic internally (Claude Code uses caching aggressively by default). Clapilot sends the user conversation through stdin and supplies the runtime rules through a protected per-request --system-prompt-file, never through argv or environment values. The system portion is deterministically bounded while preserving its beginning and end. Large MCP tool schemas and client context also use per-request protected temporary files whose paths, but not contents, are supplied to the child process. Before every Claude CLI spawn, the runtime checks the combined argv/environment byte footprint and rejects unsafe input with a value-redacted size diagnostic. This avoids Linux E2BIG failures without demoting system instructions or exposing prompts and secrets in process arguments while keeping the MCP tool surface small (see "Bridge tool specs" below).
    • Codex OAuth bridge: the Codex app-server handles OpenAI caching internally for the model loop. Clapilot's contribution is the lean MCP tool surface only.
    • Kill switch: flip Settings → ClapilotAICore → Runtime → "Prompt-Caching aktiv" off to fall back to the legacy uncached path (system as a single concatenated string for Anthropic/Bedrock, no cache_control / cachePoint, no prompt_cache_key). The flag lives in app_settings.prompt_caching_enabled (migration 149_app_settings_runtime_caching.sql) and is re-read by the agent every 5 seconds, so changes take effect on the next tool-loop step without a restart.
  2. Progressive tool disclosure. compact_tools is the effective default profile (see sessions.executeRun() auto-promotion of "full""compact_tools"). The model sees the always-on core tools plus a one-line-per-family manifest, and pulls full schemas for any other family on demand by calling tool_catalog_expand. For cache-enabled OpenAI-compatible providers, the initial compact surface is selected from stable session/page context rather than the current query text, preventing a different phrase from rewriting the early prompt/tool prefix. Query-specific tools remain reachable through the catalog expansion path.

  3. No duplicated tool catalog. buildNativeSystemPrompt() no longer emits a per-tool - name: <first sentence> text catalog (which previously duplicated the native tools: array at roughly 4–6k tokens for the current catalog size). Instead it ships a one-line summary ("N native tools are attached via the provider tools API") plus the on-demand family manifest. Flip Settings → ClapilotAICore → Runtime → "Tool-Text-Katalog im System-Prompt" on (stored in app_settings.include_tool_text_catalog) to restore the text catalog when a particular runtime (e.g. some local OpenAI-compatible models) measurably benefits from a text TOC for tool-selection accuracy.

Cache interaction with progressive disclosure: a successful tool_catalog_expand call mutates the tools array for the current provider loop and may require a new tools-cache write for that step. The following user turn starts again from the stable compact surface, so query-specific expansions do not permanently churn the session's initial cache prefix.

Observing cache effectiveness

Per-request cache counters land in two new columns on agent_model_request_logs (migration 145_agent_request_cache_tokens.sql):

  • cache_creation_input_tokens — tokens written into the provider cache on that request (Anthropic cache_creation_input_tokens, Bedrock Converse cacheWriteInputTokenCount). Costs ~1.25× normal input tokens. OpenAI and Gemini don't surface this — their write cost is folded into the regular input count.
  • cache_read_input_tokens — tokens served from the provider cache (Anthropic cache_read_input_tokens, OpenAI prompt_tokens_details.cached_tokens, Bedrock cacheReadInputTokenCount, Gemini cachedContentTokenCount). Costs ~10% of normal input tokens. This is the high-signal metric.

Extraction happens in extractUsageMetrics() (services/clapilot-agent/src/request-logs.mjs) which walks every known field name across providers. Insertion happens in recordModelRequestLog() in the same file.

The aggregated stats are exposed at Settings → Clapilot AI Core → Logs (/settings/clapilotaicore/logs), which renders ClapilotAICoreRequestLogsPanel. Four cache-specific stat cards sit beneath the existing token totals:

  • Cache-Reads — cumulative cache_read_input_tokens across matching requests.
  • Cache-Writes — cumulative cache_creation_input_tokens (only Anthropic + Bedrock will be non-zero).
  • Cache-Hit-Quotecache_read / (cache_read + input_tokens) computed only over cache-eligible requests (those with at least one non-zero cache counter). Restricting the denominator avoids the long tail of embeddings, openai_compatible requests, and pre-caching logs deflating the ratio.
  • Cache-faehige Requests — count of requests that had any cache activity at all.

Each row in the log table also shows a Cache column with R <reads> · W <writes> and the same numbers inside the expanded detail view. The raw provider usage object is still rendered in full under "Usage JSON" so any extra fields (e.g. Anthropic's cache_creation_5m_input_tokens for the 5-minute beta cache, OpenAI's audio_tokens) remain inspectable.

If you want to verify the wiring end-to-end on a fresh deployment: trigger any multi-turn Anthropic chat; from turn 2 onward the Cache-Reads counter should climb past zero, and the Cache-Hit-Quote should settle at ~80–90% on the stable tools+system prefix.

Bridge tool specs (Claude CLI bridge, Codex OAuth bridge)

Both subprocess bridges expose Clapilot's tool catalog through the same MCP stdio server (scripts/claude_clapilot_mcp.mjs) and use a two-tier surface mirroring the native compact_tools profile:

  • Direct visible tools (advertised via MCP tools/list, included in Claude CLI's --allowedTools flag, what the upstream model actually sees in its tools array): tool_catalog_search, tool_execute, plus the COMPACT_TOOLS_DIRECT_TOOL_NAMES set (context/memory/knowledge/learning recall, web search, navigation, copilot UI, session status, generated-video start/status, delegation — 23 tools). The model can call these in one round-trip without a tool_catalog_searchtool_execute detour. For the Claude CLI bridge, direct visible comes from payload.tools (typically already the compact_tools subset); for the Codex bridge, buildCodexClapilotToolSpecs() selects them from the full catalog using COMPACT_TOOLS_DIRECT_TOOL_NAMES imported from sessions/index.mjs.
  • Hidden tools (stored in the MCP spec JSON, surfaced only as tool_catalog_search response payloads): every other tool in the catalog. Tools that are already direct visible are de-duplicated from the hidden set so their schemas never appear twice in the bridge's context.

Both spec builders apply compactToolDescription (first sentence, ≤120 chars) and compactJsonSchema (per-property descriptions trimmed to 90 chars, session_key descriptions stripped at any depth) so both the visible tools API and the tool_catalog_search response payloads stay small no matter how verbose the underlying tool definitions grow. Previously the Codex builder allowed description lengths up to 20k chars per tool — the compaction reduces typical search-result payloads by ~30–40%.

Specialized-agent runs on either bridge skip the hidden catalog entirely and expose only the explicit visible allowlist (still compacted).

Cross-reference