> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Context management

> Choose explicit history, stored references, compaction, or application-side trimming.

## Choose who owns the state

| Approach | Requirements | What to preserve |
| - | - | - |
| Explicit Chat history | Ordinary stateless conversations | Ordered messages and complete tool calls with results |
| Explicit Responses replay | A native path supporting input items | All required output items, including ciphertext, IDs, `phase`, and metadata |
| `previous_response_id` | Upstream state retention and compatible routing | Prior response ID; do not assume it carries instructions that must remain active next turn |
| Manual `/v1/responses/compact` | A channel supporting compaction | The complete compacted window and a backup of original history |
| Application trimming | The business can discard older content | Persistent constraints, latest question, and complete turn boundaries |

Caching reuses input computation without enlarging the model's context window. A Responses state ID does not bypass capacity limits either. Reserve output and required reasoning capacity, including tool definitions, media, and protocol overhead. Characters, UTF-8 bytes, and message counts are not token counts. See [coverage](/en/guides/api-coverage) for counting-endpoint boundaries; do not assume a usable general token-counting API exists.

## Automatic truncation and context editing boundaries

The native Responses DTO accepts `truncation` and `context_management`; upstream support and semantics depend on the model and channel. Current Responses → Chat conversion rejects stateful fields including `context_management` and `previous_response_id`, and has no mapping copying `truncation`. Do not rely on automatic trimming after conversion.

Claude `context_management` is a protocol-specific object; Claude → Responses conversion does not copy it verbatim. OpenRouter's `transforms: ["middle-out"]` is also outside this site's verified general contract. Start with the existing [explicit compaction example](/en/guides/compaction) when compression is needed. Identically named fields across protocols do not establish equivalent implementations.

## Runnable: a local window of complete text turns

This example handles plain-text Chat history without tools, media, or reasoning state. It keeps `system` and the latest `question`, removing oldest complete user / assistant pairs to fit a turn limit and a UTF-8 limit on serialized messages. It neither estimates tokens nor guarantees a fit within the model's window, and does not call a summarization model.

Create `history.json`:

```json theme={null}
{
  "system": "Help plan travel. Always preserve the no-driving constraint.",
  "turns": [
    {"user": "Suggest a destination.", "assistant": "Kyoto has convenient public transport."},
    {"user": "Suggest an area.", "assistant": "Consider eastern Kyoto for your first day."}
  ],
  "question": "Suggest a rainy-day activity."
}
```

Use the [REST runtime](/en/guides/http-runtime), keeping `examples/context-window.mjs` and `http-client.mjs` together:

```sh theme={null}
export API_ROOT_URL='https://gateway.example.com'
export API_KEY='YOUR_API_KEY'
export API_MODEL='YOUR_CHAT_MODEL_ID'
export API_HISTORY_FILE='./history.json'
export API_CONTEXT_KEEP_TURNS='1'
export API_CONTEXT_MAX_BYTES='4096'
node examples/context-window.mjs
```

```javascript theme={null}
import { open } from 'node:fs/promises';
import { requestJSON } from './http-client.mjs';

try {
  const { API_MODEL, API_HISTORY_FILE } = process.env;
  const keep = Number(process.env.API_CONTEXT_KEEP_TURNS || '4');
  const budget = Number(process.env.API_CONTEXT_MAX_BYTES || '4096');
  if (!API_MODEL?.trim() || !API_HISTORY_FILE || !Number.isInteger(keep) || keep < 0 || keep > 20
    || !Number.isInteger(budget) || budget < 128 || budget > 65_536) throw new Error('Invalid configuration.');
  const file = await open(API_HISTORY_FILE, 'r');
  let input;
  try {
    if (!(await file.stat()).isFile()) throw new Error('Expected a regular file.');
    const buffer = Buffer.alloc(131_073);
    let bytesRead = 0;
    while (bytesRead < buffer.length) {
      const part = await file.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);
      if (!part.bytesRead) break;
      bytesRead += part.bytesRead;
    }
    if (bytesRead > 131_072) throw new Error('History exceeds example file limit.');
    input = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, bytesRead)));
  } finally { await file.close(); }
  if (!input || Object.keys(input).sort().join(',') !== 'question,system,turns'
    || typeof input.system !== 'string' || !input.system.trim()
    || typeof input.question !== 'string' || !input.question.trim() || !Array.isArray(input.turns)
    || input.turns.some((turn) => !turn || Object.keys(turn).sort().join(',') !== 'assistant,user'
      || typeof turn.user !== 'string' || !turn.user.trim()
      || typeof turn.assistant !== 'string' || !turn.assistant.trim())) {
    throw new Error('Expected text-only complete turns.');
  }
  const turns = keep === 0 ? [] : input.turns.slice(-keep);
  let messages;
  while (true) {
    messages = [{ role: 'system', content: input.system },
      ...turns.flatMap((turn) => [{ role: 'user', content: turn.user }, { role: 'assistant', content: turn.assistant }]),
      { role: 'user', content: input.question }];
    if (Buffer.byteLength(JSON.stringify(messages), 'utf8') <= budget) break;
    if (!turns.length) throw new Error('Required instructions and question exceed local byte budget.');
    turns.shift();
  }
  const result = await requestJSON('/v1/chat/completions', { model: API_MODEL, messages });
  const choice = result.choices?.[0];
  const message = choice?.message;
  if (!Array.isArray(result.choices) || result.choices.length !== 1 || choice?.index !== 0
    || choice.finish_reason !== 'stop' || message?.role !== 'assistant'
    || message.refusal != null || message.tool_calls != null || message.function_call != null
    || typeof message.content !== 'string' || !message.content.trim()) throw new Error('Incomplete text response.');
  console.log(JSON.stringify({ answer: message.content, retained_turns: turns.length,
    omitted_turns: input.turns.length - turns.length, messages_bytes: Buffer.byteLength(JSON.stringify(messages), 'utf8') }));
} catch {
  console.error('Context example failed. Check the text-only history, local budget, and request logs. Original file is unchanged.');
  process.exitCode = 1;
}
```

The input file is limited to 128 KiB; `KEEP_TURNS` ranges from 0–20 and `MAX_BYTES` from 128–65536. These are local example limits, not server or model limits. Unknown input fields, incomplete text turns, invalid UTF-8 / JSON, or required instructions and the latest question exceeding the budget fail before a request. Strings are never truncated.

Success output contains the answer, retained / omitted turn counts, and sent message bytes. The original file remains intact and failures are not retried automatically. Omitting history changes semantics. Keep indispensable business constraints in trusted persistent state rather than assuming an older conversation stays within the window.

## Tools, summaries, and reasoning state

Keep tool calls and results together rather than deleting one side. Reasoning ciphertext, native signatures, and compaction items are protocol state and cannot be handled by this text-trimming script. Verify unchanged replay using the [reasoning example](/en/guides/reasoning-models), or preserve the complete returned window with the [compaction example](/en/guides/compaction).

If the application generates summaries, retain facts, user constraints, pending tasks, and source references, and establish which details the business allows it to discard. Summaries are not lossless compression. Do not automatically promote user content or tool output to system instructions. Keep original history if compaction fails, then validate before updating session state. Context-limit errors should not trigger uncontrolled regeneration.

Sources: revision `30a6d88a4`, Responses / Claude DTOs, `relaykit/relayconvert/internal/oai_responses/to_oai_chat_req.go`, and `relaykit/relayconvert/internal/claude_messages/to_oai_responses_req.go`. References: [OpenRouter message transforms](https://openrouter.ai/docs/guides/features/message-transforms) and [OpenAI conversation state](https://developers.openai.com/api/docs/guides/conversation-state).

[Conversations](/en/guides/conversations) · [Caching](/en/guides/prompt-caching) · [Responses API](/en/api-reference/responses)

[阅读中文](/zh/guides/context-management)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.