← Back to Plugins
Voice

Jev Compaction

SqaaSSL By SqaaSSL 👁 38 views ▲ 0 votes

Verbatim context compaction for OpenClaw: a context engine powered by TypeSafe's Jev. Drops stale tool calls and results, never summarizes.

Homepage GitHub

Install

npm install &&

README

# openclaw-jev-compaction

**Verbatim context compaction for [OpenClaw](https://docs.openclaw.ai).**
A context engine that asks [TypeSafe's Jev](https://docs.typesafe.ai) which
tool calls and tool results a session still needs, drops the rest, and never
summarizes. What stays is the original text, word for word.

[![CI](https://github.com/SqaaSSL/openclaw-jev-compaction/actions/workflows/ci.yml/badge.svg)](https://github.com/SqaaSSL/openclaw-jev-compaction/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/openclaw-jev-compaction)](https://www.npmjs.com/package/openclaw-jev-compaction)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Built by the [Hilo team](https://hilo.cx). Inspired by
[tamaratran/fast-jev-compaction](https://github.com/tamaratran/fast-jev-compaction),
the Claude Code plugin that first put this idea to work; see
[Credits](#credits).

## Why

Every long agent session hits the context window. The usual answer is to ask
an LLM to summarize the old turns, and a summary is lossy: a file path, an
exact error, a constraint the user gave, a command that was run can all
disappear, precisely when the model needs them later.

Tool calls and their outputs are most of the bulk, and most of them are
spent: a directory listing already used, a passing test run, a file read
before it was rewritten. This engine deletes only those, and only after a
model built for exactly this kind of judgment has looked at the whole
conversation. User and assistant text is never rewritten. A single Jev
request scores dozens of calls in about a second.

```
jev-compaction: post-turn compaction for 6f1c…: 71% fewer chars (2 kept, 3 results truncated, 9 calls dropped, 4 pinned, 5 protected); ~48210 → ~13980 tokens; state ~11840 tokens (full) in 1 request(s), 1310 ms
```

## Install

You need a TypeSafe API key. Then:

```sh
openclaw plugins install openclaw-jev-compaction --accept-capabilities
```

`--accept-capabilities` consents to the plugin taking OpenClaw's exclusive
context-engine slot. The install selects the engine and enables the entry;
restart the Gateway afterwards.

Give it the key either through the environment of the Gateway process
(`TYPESAFE_API_KEY`) or in `openclaw.json`:

```json5
{
  plugins: {
    slots: { contextEngine: "jev-compaction" },
    entries: {
      "jev-compaction": {
        enabled: true,
        config: {
          apiKey: "apikey_...",     // or { source: "env", id: "TYPESAFE_API_KEY" }
          compactAtPercent: 60,
        },
      },
    },
  },
}
```

`openclaw plugins inspect jev-compaction --runtime` should report
`Status: loaded` with the `context-engine` capability, and
`openclaw plugins doctor` should pass. To go back to the built-in behavior,
set `contextEngine` to `"legacy"` or remove it; sessions carry on either way.

From a checkout: `npm install && npm run build`, then
`openclaw plugins install -l /path/to/openclaw-jev-compaction --force --accept-capabilities`.
OpenClaw loads the compiled bundle, so rebuild after edits and restart the
Gateway.

## How it works

The plugin registers the `jev-compaction` context engine and owns compaction,
so OpenClaw's built-in summarizer steps aside and the engine decides what the
model sees:

| Lifecycle point | What happens |
| --- | --- |
| `assemble`, before every model call | The session's stored decisions are applied to the history. If it is still above `compactAtPercent` of the token budget, one Jev pass runs first. |
| `afterTurn` | A pass runs when the session has crossed the threshold, so the next turn starts lean without waiting. |
| `compact`, on `/compact` and overflow recovery | A forced pass, with any text after `/compact` added to the task Jev is told about. If verbatim compaction cannot get the session under budget, the engine hands over to OpenClaw's summarizer (`fallback: "summarize"`). |
| `commitTurn` | Advancement keys are recorded so retried turns stay idempotent. |

A pass works like this:

1. Every tool call is paired with its result. Calls in the first message or
   in the newest `preserveRecentMessages` messages are pinned and never
   touched.
2. Some calls are kept by rule, without a question: failed calls, calls of
   tools that cannot be re-run (`protectTools`), edits (`editTools`), and the
   newest read of a file that was edited afterwards, so a kept read never
   shows a file as it no longer is.
3. The state sent to Jev is the whole conversation, oldest first. Each tool
   result becomes a note with its outcome, its size, and the first and last
   `resultPeekChars` characters of the output. Inputs and texts are included;
   nothing is summarized. The state is fitted into Jev's limits in stages,
   and when a long history only fits by collapsing old messages, each batch
   of questions is scored against a state where its own messages stay in
   full.
4. For every remaining call Jev answers two yes/no questions: does the call
   still carry information the task depends on, and does its output hold
   information the assistant would need again. Both come with criteria.
5. Decisions per call: result score at or above `keepResultThreshold` (0.25)
   keeps call and result; else call score at or above `keepCallThreshold`
   (0.5) keeps the call and truncates the result to a head plus a note; else
   the call goes together with its result. Result scores run on a lower
   scale than call scores, hence the two thresholds.
6. The history is rebuilt. An assistant turn that keeps its text but loses
   tool calls gets a one-line note saying so, and once a session has pruned
   history the engine prepends a short system prompt note asking the model
   to verify state with tools before reporting work as done. Without those,
   a model can learn from its own trimmed history that work can be reported
   without evidence; we have seen it happen.

Decisions are kept per session in small JSON files under
`<agentDir>/jev-compaction/` (override with `stateDir`), so they survive
Gateway restarts. The persisted transcript is never modified; the engine only
changes what is assembled for the model, the same way session pruning does.

### Safety nets

- A pass in which Jev scored at least `keepSignalMinScored` calls and kept
  none is indistinguishable from Jev answering zero to everything. It is
  logged and not applied; on `/compact` the summarizer takes over.
- A pass that adds nothing is not repeated until the session has grown, so a
  session Jev wants to keep whole does not pay for a request on every
  tool-loop iteration.
- If the key is missing or Jev is unreachable, `assemble` sends the history
  as is and logs a warning. Requests have a deadline, are retried on rate
  limits and server errors, and honor the host's abort signal.
- Answers are validated: a probability outside 0–1, a malformed answer map,
  or an ambiguous history (a tool id used twice, two results for one call) is
  an error, never a deletion.

### What leaves the machine

The state and the questions go to the TypeSafe API. Credential-shaped tool
input fields (`token`, `password`, `authorization`, …) are sent as
`[REDACTED]`, and secret-shaped strings (bearer headers, provider key
formats, `key=value` pairs under credential names) are masked in inputs and
in the result excerpts. User and assistant text is sent as is; nothing scans
it. Set `resultPeekChars: 0` to send no output content at all.

## Options

All options live under `plugins.entries.jev-compaction.config`.

| Option | Default | Description |
| --- | --- | --- |
| `apiKey` | `TYPESAFE_API_KEY` | TypeSafe API key, as a string or an env SecretRef |
| `model` | `jev-latest` | Jev model (`jev-latest` resolves to `jev-1.13.0`; pin the full version) |
| `baseUrl` | System One endpoint | TypeSafe API URL |
| `compactAtPercent` | `60` | Estimated context usage (% of the token budget) at which a pass runs |
| `keepCallThreshold` | `0.5` | Minimum probability for a call to stay |
| `keepResultThreshold` | `0.25` | Minimum probability for a result to stay verbatim |
| `keepThreshold` | | Sets both thresholds when the specific ones are not given |
| `questionStyle` | `useful` | `useful` asks whether a call or output still serves the task; `recoverable` asks whether re-running the tool would not do |
| `preserveRecentMessages` | `6` | Newest messages never touched (the first is always kept) |
| `protectTools` | Agent, Task, AskUserQuestion, sessions_spawn, sessions_send, subagents, message | Tools whose calls and results are always kept; replaces the list |
| `editTools` | Edit, Write, MultiEdit, NotebookEdit, edit, write, apply_patch | Tools that change files; replaces the list |
| `resultPeekChars` | `200` | Characters from the start and end of each result shown to Jev; `0` shows only the size |
| `truncateHeadChars` | `300` | Characters of a dropped tool result kept before the note |
| `minReductionRatio` | `0.1` | A pass that frees less than this while the session is still over budget triggers the fallback |
| `fallback` | `summarize` | `summarize` delegates to OpenClaw's built-in compaction when verbatim compaction is not enough; `none` reports failure instead |
| `maxStateTokens` | `25000` | Estimated token ceiling for the state sent to Jev |
| `maxRequestTokens` | `60000` | Estimated ceiling for state plus one batch of questions (Jev allows 64k; the state plus one question is kept under 32k) |
| `maxConcurrentRequests` | `4` | Jev requests in flight at once |
| `retries` | `2` | Retries per request on a rate limit, server error or transport failure |
| `timeoutMs` | `15000` | Deadline for one Jev request |
| `goal` | last 3 user prompts | Task description included in the Jev state |
| `stateDir` | `<agentDir>/jev-compaction` | Where per-session decision files live |

## Measure it on your own sessions

Compaction is a judgment; measure it before trusting a setting. The replay
script runs recorded sessions through the engine without touching them and
prints what would be 

... (truncated)
voice

Comments

Sign in to leave a comment

Loading comments...