History planning
Choose exact, recent, or managed projections without mutating canonical messages.
const result = await generate(supportReply, {
model,
messages,
input: { ticketId: "ticket_4821" },
});Caller-owned messages are the canonical transcript. Crux may project a
model-facing view when you declare a history policy, but it never changes the
source array or infers a hidden transcript window.
Choose one policy
| Requirement | Policy |
|---|---|
| Every historical detail must stay exact | Bare caller-owned messages |
| Only the newest exact turns matter | history.recent() |
| Older turns may become derived evidence | history() |
Do not add more than one projection. If conditional resolution activates both
history.recent() and history(), planning fails with
RequestCompositionError: INVALID_COMPOSITION.
Keep complete exact history
Use bare messages when every prior turn is required:
await generate(supportReply, {
model,
messages: transcript,
input: { ticketId: "ticket_4821" },
});This is the safest choice for short conversations or regulated records that
cannot lose detail. It is not bounded. Development warnings appear as exact
history approaches the optimization watermark. When it no longer fits, Crux
throws REQUEST_TOO_LARGE before provider dispatch.
Keep a recent exact suffix
Use history.recent() when older turns can be omitted but retained turns must
stay byte-for-byte canonical:
import { history, prompt } from "@use-crux/core";
const supportReply = prompt({
id: "recent-support",
use: [history.recent({ messages: 12, tokens: 8_000 })],
prompt: "Continue the support conversation.",
});Use a number for a message-only cap:
history.recent(12)messages and tokens are soft at causal-group boundaries. The projection
keeps a Tool call with its result and retains the newest indivisible group even
when that group exceeds a cap. A contiguous leading system-only prefix stays
outside the conversational caps.
Use this policy when you want no support model call, no summary artifact, and no Storage requirement. Do not use it when older decisions must remain visible in some form.
Invalid, zero, negative, or fractional caps throw TypeError. A soft boundary
adjustment appears as a content-free warning in the request receipt.
Manage long-running history
Use history() when old turns may become a summary while recent turns remain
exact:
import { history, prompt, summarize } from "@use-crux/core";
const supportReply = prompt({
id: "managed-support",
use: [
history({
recent: { messages: 10, tokens: 8_000 },
summary: {
model: summaryModel,
strategy: summarize.adaptive(),
},
onMiss: "inline",
}),
],
prompt: "Continue the support conversation.",
});A managed summary is assistant-role derived evidence. It never becomes a system instruction. The exact leading system prefix remains canonical.
Start with defaults
const supportReply = prompt({
id: "managed-support",
use: [history()],
prompt: "Continue the support conversation.",
});The default recent suffix is derived from the resolved model and complete
request. The default support model is the response model, the strategy is
summarize.adaptive(), onMiss is inline, and compatible provider-native
summary paths are allowed.
Choose a miss policy
| Value | Use when | Behavior |
|---|---|---|
inline | Correctness requires older evidence and first-use latency is acceptable. | Joins or runs required preparation before the main request. |
recent-only | The exact recent suffix is an acceptable degraded view. | Proceeds without an older summary. |
fail | Missing derived evidence should stop the request. | Throws before provider dispatch. |
Missing infrastructure does not silently change inline into recent-only.
If the full transcript fits the strict maximum, Crux may use it for the current
call and prepare a summary for later without adding latency only to cross the
soft watermark.
Choose a summary strategy
| Strategy | Good fit |
|---|---|
summarize.adaptive() | Default for conversations whose size and update pattern vary. |
summarize.regenerate() | Smaller prefixes where minimizing derived drift matters. |
summarize.rolling() | Frequent incremental growth over deterministic ranges. |
summarize.hierarchical() | Prefixes too large for one support call. |
history({
summary: { strategy: summarize.hierarchical() },
providerNative: false,
})Set providerNative: false when replay or compliance requires portable
Core-managed lowering. Every support call remains linked through receipt
inspection.
Understand source precedence
Crux projects the first source present for the invocation:
- Call-site
messages. - Prompt-level messages.
- An active canonical Thread path when available.
- No history.
Arrays are never merged. Call-site messages are a complete manual transcript and suppress Prompt-level message content for that invocation. Manual transcripts do not imply a commit to another owner.
When a projection has no source, Crux emits an actionable diagnostic rather than silently doing nothing.
Inspect the selected view
const result = await generate(supportReply, {
model,
messages: transcript,
});
const receipt = result.steps[0].request;
const inspection = await receipt.inspect();
console.log(receipt.adaptations);
console.log(inspection.artifacts);
console.log(inspection.supportRequests);Receipts identify summary selection, boundary warnings, and support request ids. Inspection never includes transcript or summary text.
Error behavior
| Error | Cause |
|---|---|
TypeError | Invalid recent limits, miss policy, or strategy value. |
RequestCompositionError: INVALID_COMPOSITION | Multiple active projections or an invalid history placement. |
RequestCompositionError: REPRESENTATION_UNAVAILABLE | A required summary cannot be prepared or accessed. |
RequestCompositionError: REQUEST_TOO_LARGE | The minimum legal history view still exceeds the strict limit. |
Related
- Reference: Request history
- Guide: Input budgets
- Guide: Representation ladders
- Guide: Receipts and preview