Skip to main content
AI tutorials

Preserve Exact Claude Code Context with fast-jev-compaction

Learn how to install and use fast-jev-compaction as an npm library or Claude Code plugin. This tutorial explains its Jev-based decisions, configuration options, custom transport support, failure behavior, and techniques for safely reducing long transcripts while preserving important text verbatim.

Preserve Exact Claude Code Context with fast-jev-compaction

What fast-jev-compaction does

fast-jev-compaction is an npm library and Claude Code plugin for reducing long agent transcripts without rewriting them into a summary. Instead, it asks the Jev model to score old tool calls and tool results, then keeps, truncates, or removes them according to configurable thresholds.

User and assistant text remains verbatim and in its original order. Only tool calls and their matching results are candidates for removal. This avoids losing exact constraints, file paths, commands, and error messages through conventional summarization.

The repository contains both the reusable package under src/ and a Claude Code function-hook plugin under hooks/ and .claude-plugin/.

How compaction works

  1. Each tool_use is paired with its tool_result through tool_use_id.

  2. Calls in the first message and the newest preserveRecentMessages messages are pinned, so they are not modified.

  3. The complete conversation is presented to Jev from oldest to newest. Tool-result contents are replaced in this decision state by short notes such as ok, 4213 chars (omitted), while tool inputs and text remain available.

  4. For every non-pinned call, Jev answers two questions: whether knowing about the call still matters, and whether the full result must remain available verbatim.

  5. The returned probabilities are compared with keepThreshold.

  6. The transcript is rebuilt without leaving an orphaned result behind.

Decision outcomes

  • If the result score meets the threshold, both the call and complete result are retained.

  • If only the call score meets the threshold, the call remains and the result is shortened to its first truncateHeadChars characters followed by a note.

  • If neither score meets the threshold, the call and its result are removed together.

Messages that become empty are removed. Messages that do not need changes are returned as the same objects.

Fitting large histories into Jev requests

The decision state has a default ceiling of 25000 estimated tokens. If the original state is too large, the library progressively shortens the representation shown to Jev. It truncates tool inputs, abridges long text using the head and tail, collapses old messages, reduces old calls to one-line records, omits old call-less messages, and folds runs of old call-only messages together.

These transformations affect the state used for scoring, not the preserved text in the final transcript. If the state still cannot fit, compaction throws an error.

Questions are also divided into batches so that each state-plus-question request stays below maxRequestTokens, which defaults to 30000. Requests run concurrently, and their decisions are merged.

Install the npm library

Install the package and provide a TypeSafe API key through the environment:

npm install fast-jev-compaction
export TYPESAFE_API_KEY=...

Do not commit the API key or place it directly in a source file.

The default client reads process.env.TYPESAFE_API_KEY. You may also supply an apiKey option when calling the API.

Compact a transcript

Import compactMessages, prepare a transcript, and pass any options that differ from the defaults:

import {
  compactMessages,
  reductionRatio,
  type Message,
} from 'fast-jev-compaction';

const transcript: Message[] = [
  {
    role: 'user',
    text: 'Fix the failing test. Never edit src/generated.',
    toolUses: [],
  },
  {
    role: 'assistant',
    text: '',
    toolUses: [
      {
        tool_use_id: 'toolu_1',
        tool: 'Read',
        input: { file_path: 'src/a.ts' },
      },
    ],
  },
  {
    role: 'user',
    text: '',
    toolUses: [],
    toolResults: [
      { tool_use_id: 'toolu_1', text: '…file contents…' },
    ],
  },
];

const result = await compactMessages(transcript, {
  preserveRecentMessages: 4,
});

console.log(result.messages);
console.log(result.decisions);
console.log(result.stats);

The exported Message type is a subset of Claude Code's SessionMessage, so Claude Code session transcripts can be passed to the package directly.

Inspect whether compaction was worthwhile

The package exports reductionRatio, which can help an application decide whether to use the compacted history:

const result = await compactMessages(transcript);

if (reductionRatio(result) < 0.25) {
  // Keep the original transcript or use another fallback.
} else {
  // Continue with result.messages.
}

The result statistics include message and character counts before and after compaction, decision counts grouped by reason, the estimated state size, the fitting stage used, and the number of Jev requests.

Configure compaction behavior

The main options are:

  • apiKey: TypeSafe API key. It defaults to TYPESAFE_API_KEY.

  • model: Jev model name. The default is jev-latest.

  • baseUrl: System One endpoint. The default is https://api.typesafe.ai/v1/systemone.

  • fetch: injectable fetch implementation, particularly useful in tests.

  • goal: ongoing task description included in the state. By default, it uses the last three user prompts.

  • keepThreshold: minimum probability required to keep a call or full result. The default is 0.5.

  • preserveRecentMessages: number of newest messages that cannot be modified. The default is 6; the first message is also always preserved.

  • maxStateTokens: estimated token ceiling for the decision state. The default is 25000.

  • maxRequestTokens: estimated ceiling for state plus questions in one request. The default is 30000.

  • truncateHeadChars: number of leading characters retained when a result is truncated. The default is 300.

const result = await compactMessages(transcript, {
  goal: 'Fix the test without editing generated files',
  keepThreshold: 0.6,
  preserveRecentMessages: 8,
  truncateHeadChars: 500,
});

A higher keepThreshold requires stronger keep probabilities and can therefore remove more tool activity. Because a probability is not proof that deletion is safe, evaluate threshold changes against representative transcripts.

Use a custom Jev transport

If the built-in TypeSafe client does not fit your environment, implement the exported JevAsker interface. It requires an ask(state, questions) method. Then call compact(messages, asker, options) instead of compactMessages.

The package also exports buildJevRequest for constructing the HTTP request body and parseJevResponse for response validation. Lower-level building blocks are available for more specialized integrations:

  • collectToolCalls pairs calls with their results.

  • fitState prepares a state within the configured size ceiling.

  • batchCalls divides questions across requests.

  • decideCall converts scores into a keep, truncate, or remove decision.

  • applyDecisions rebuilds the message history.

Install the Claude Code plugin

The plugin requires Claude Code 2.1.274 or later. Function hooks are an early-access feature, so enable them wherever Claude Code runs. For example, add the environment settings to ~/.claude/settings.json:

{
  "env": {
    "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1",
    "TYPESAFE_API_KEY": "<your key>"
  }
}

Add the repository as a plugin marketplace and install the plugin:

claude plugin marketplace add tamaratran/fast-jev-compaction
claude plugin install fast-jev-compaction@fast-jev-compaction

The installer prompts for options such as thresholds and truncateHeadChars. Leave the API-key option at its default to use TYPESAFE_API_KEY from the environment.

Restart Claude Code or run:

/reload-plugins

After activation, both /compact and automatic compaction pass through the Jev hook. A successful replacement reports a toast similar to fast-jev-compaction: kept N/M messages, no summary (...). If Jev fails or the reduction is insufficient, the hook falls back to Claude Code's built-in summary.

Run the plugin directly from a checkout

You can test the repository without publishing or installing it:

CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude --plugin-dir .

Run this command from the repository root.

Advanced usage tips

Protect active work

Increase preserveRecentMessages when recent tool outputs are likely to be reused. Calls in these messages are pinned and cannot be removed or truncated.

Provide a precise goal

The default goal is derived from the last three user prompts. For long or shifting sessions, explicitly setting goal can give Jev a clearer description of the ongoing task.

Use statistics to define fallback policy

Inspect result.stats and reductionRatio(result) rather than accepting every compacted result automatically. Short sessions may not produce enough reduction to justify replacing the original transcript.

Plan for errors

Compaction throws when Jev fails, returns malformed data, omits a required key, or receives a history that cannot be fitted within the state limit. Wrap library calls in error handling and retain the original transcript or invoke another compaction strategy when necessary. The Claude Code hook already falls back to its built-in summary.

Understand request cost

The full fitted state is repeated for every question batch. A history near the state ceiling may therefore require one large request for each small group of questions. Review the request count in result.stats when tuning limits.

Test without network calls

The repository's unit tests use a fake Jev implementation and never contact TypeSafe. A custom JevAsker or injected fetch implementation can support the same approach in your application tests.

Development commands

When working from the repository, install dependencies and run the available checks:

npm install
npm run typecheck
npm test
npm run build
npm run validate:plugin
TYPESAFE_API_KEY="$(cat ~/.typesafe_key)" npm run demo

The demo command performs the live network check. The repository also contains a macOS SwiftUI animation under demo/JevDemo. It dramatizes compaction with a canned transcript and does not call the API:

demo/JevDemo/build.sh

Press space in the app to replay the animation.

Important limitations

  • Only tool calls and results can be removed or truncated. Text messages remain unchanged in the output.

  • Token counts are estimates derived from character patterns rather than a tokenizer.

  • Jev's probabilities do not guarantee that a result is safe to delete, although the assistant can re-run a removed tool call when necessary.

  • Every request batch repeats the full state, which can increase cost for histories close to the configured ceiling.

Conclusion

fast-jev-compaction offers an alternative to lossy conversation summaries by preserving user and assistant text while selectively pruning stale tool activity. Use the npm API for application-level control, the Claude Code plugin for automatic integration, and the provided thresholds, preservation settings, statistics, and fallback behavior to tune compaction for your workflow.