Skip to main content
Version: 1.0.0

Bound context and understand compaction

Goal: avoid sending a request beyond the configured context ceiling while keeping prompt size, retained history and permissions distinct.

1. Start from a working agent​

Use the native calculator tutorial. Its Touba program remains unchanged. Use this provider:

providers/calculator.provider.mjs
import { createOpenAiDecisionProvider } from "@touba/sdk";

export const decideAgentCapability = createOpenAiDecisionProvider({
model: "gpt-4.1-mini",
decisionInterface: "native",
maxOutputTokens: 1000,
maxRetries: 0,
context: {
contextWindowTokens: 32768,
safetyMarginTokens: 512,
inputTokenMultiplier: 1.2,
onEvent(event) { console.error(JSON.stringify(event)); },
},
history: {
maxMessages: 128,
maxBytes: 262144,
maxRetainedBytes: 8 * 1024 * 1024,
onEvent(event) { console.error(JSON.stringify(event)); },
},
});

32768 is an application ceiling here, not the model's advertised maximum. Verify that your model supports at least this window. Changing models requires rechecking the window, output reservation and provider parameters. context explicitly enables counting; the language embeds no aging model catalogue.

2. Read the events​

Run the calculator using the native tutorial's command. Official counting precedes each generation and receives the serialized body, including system instructions, tools, schemas and history. Events go to stderr, keeping the CLI's JSON result on stdout.

For example, with 1000 counted input tokens these options reserve 1200 + 512 + 1000 = 2712 tokens. The last term reserves the response, including reasoning accounted as output by the provider. Counts may be estimates; the margin covers reasonable variation without guaranteeing future external API behavior.

Event / errorMeaning
provider_context_count, fits: truethe ceiling permits sending
fits: falseno generation; attempt local context reduction
decision_history_contextmessage size and exchanges omitted from the window
ProviderContextWindowExceededeven the retained minimum does not fit
ProviderContextCountUnavailablecounting unavailable; generation was not sent

3. Trigger a controlled refusal​

In a copy of the provider, set contextWindowTokens: 100, keeping maxOutputTokens: 1000. Run the calculator again: the output reservation alone exceeds the ceiling. Expect refusal before generation and before selecting any skill. If counting is unavailable, that error takes precedence; generation still is not sent.

Restore the original ceiling afterwards. Do not classify this refusal as a successful business result. The runtime error's provider_error_code exposes the SDK diagnosis when available.

4. Understand what is reduced​

The SDK removes the oldest complete exchanges from sent context. A native call stays paired with its result and original metadata. The latest exchange and all current runtime observations remain. Instructions, capability signatures and permissions are not summarized.

Originals remain in the store until expiration, explicit deletion or a storage capacity refusal. If current observations alone are too large, no summary can bypass that limit: reconsider result size, budgets or work decomposition.

5. Add a summary only when needed​

Default compaction calls no summarizer. If your application has a qualified summary service, provide history.summarize. Its contract is documented in the history guide. It receives outgoing exchanges and the previous summary, and returns bounded text. The SDK adds it as untrusted data with provenance and counts the request again. Original exchanges remain available in storage.

A summary may omit details or contain malicious instructions. It is never evidence of authorization or human approval. If the service uses an LLM, the host must account for its costs, quotas and timeouts; these extra calls are not runtime decisions.

6. Use Anthropic or Gemini​

The guard is independent of Touba syntax. With the AI SDK connectors installed in your application, configure their transport:

providers/claude.provider.mjs
import { createAnthropic } from "@ai-sdk/anthropic";
import { createAiSdkDecisionProvider, createProviderContextFetch } from "@touba/sdk";

const transport = createProviderContextFetch({
provider: "anthropic",
contextWindowTokens: 32768,
});
const anthropic = createAnthropic({ fetch: transport });

export const decideAgentCapability = createAiSdkDecisionProvider({
model: anthropic(process.env.ANTHROPIC_MODEL),
decisionInterface: "native",
maxOutputTokens: 1000,
maxRetries: 0,
});

For Gemini, use createGoogleGenerativeAI from @ai-sdk/google with provider: "google", an explicit model and GOOGLE_GENERATIVE_AI_API_KEY. Your application must install these connectors and configure their keys. These examples target official direct endpoints, not Bedrock or Vertex. The guard refuses unsupported endpoints without sending the request.

A token ceiling does not replace a monetary budget, Touba budgets or business validation. Also see the SQLite tutorial.