@chroxify/eunoe 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +315 -236
- package/package.json +3 -2
- package/src/cli/args.ts +1 -1
- package/src/cli/commands/index.ts +16 -2
- package/src/cli/commands/mode.ts +53 -17
- package/src/cli/commands/serve.ts +2 -1
- package/src/cli/commands/settings.ts +100 -0
- package/src/cli/commands/start.ts +64 -0
- package/src/cli/commands/status.ts +50 -16
- package/src/cli/commands/stop.ts +33 -11
- package/src/cli/commands/uninstall.ts +11 -9
- package/src/cli/constants.ts +28 -5
- package/src/cli/help.ts +2 -2
- package/src/cli/launchd.ts +8 -3
- package/src/cli/prompt.ts +91 -0
- package/src/cli/service.ts +33 -0
- package/src/cli/sessions.ts +25 -0
- package/src/cli/stats.ts +73 -0
- package/src/cli/types.ts +1 -0
- package/src/cli/ui.ts +15 -0
- package/src/config/constants.ts +15 -3
- package/src/config/index.ts +55 -6
- package/src/config/types.ts +34 -5
- package/src/context/constants.ts +35 -0
- package/src/context/content.ts +23 -1
- package/src/context/evidence.ts +92 -0
- package/src/context/recall.ts +155 -0
- package/src/context/rerank.ts +44 -0
- package/src/context/strategies.ts +6 -6
- package/src/context/turns.ts +19 -14
- package/src/context/types.ts +43 -1
- package/src/proxy/canonical.ts +32 -0
- package/src/proxy/compaction.ts +29 -12
- package/src/proxy/constants.ts +10 -0
- package/src/proxy/fingerprint.ts +15 -0
- package/src/proxy/rewrite.ts +84 -10
- package/src/proxy/server.ts +30 -12
- package/src/proxy/sessions.ts +46 -0
- package/src/proxy/types.ts +24 -0
- package/src/transcript/constants.ts +0 -1
- package/src/transcript/locate.ts +1 -1
- package/src/transcript/messages.ts +29 -0
- package/src/cli/commands/install.ts +0 -44
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
**Context management for Claude Code that never forgets what matters.**
|
|
8
8
|
|
|
9
|
-
A local proxy that
|
|
10
|
-
no
|
|
9
|
+
A local proxy that keeps long Claude Code sessions sharp. Smaller context,
|
|
10
|
+
nothing lost, no pause, no restart.
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -19,310 +19,389 @@ no two-minute pause, no agent that wakes up having forgotten what you told it.
|
|
|
19
19
|
|
|
20
20
|
## The problem
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
Every long Claude Code session eventually turns dumb on you, and it happens
|
|
23
|
+
well before the context window is full. The agent starts forgetting rules you
|
|
24
|
+
gave it an hour ago, re-reading files it already read, and asking about
|
|
25
|
+
decisions you made together. When compaction finally kicks in it makes things
|
|
26
|
+
worse, because the whole conversation gets replaced by a summary and anything
|
|
27
|
+
the summary left out is gone for good.
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
At that point the only fix is a fresh session, which means explaining the
|
|
30
|
+
project again, pasting the rules again, and hoping it gets further this time.
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
agent Here's the deploy command — want me to run it?
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
The instruction was five turns old and three paragraphs into a 21,000-character
|
|
36
|
-
summary. So were the exact version ID from the last deploy, the error you spent
|
|
37
|
-
two turns tracking down, and the approach you already ruled out.
|
|
32
|
+
The reason is that every file the agent ever opened and every command it ever
|
|
33
|
+
ran stays in the prompt, so two hundred turns in the model is still reading a
|
|
34
|
+
log from the first hour on every request, with the thing you said five turns
|
|
35
|
+
ago buried somewhere in the middle of it.
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
| | Claude Code | eunoe |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| **context** | grows with every tool call until it fills the window | finished turns fold to your message, the output lines that mattered and the reply; turns in progress go out whole |
|
|
40
|
+
| **at the limit** | a two-minute summary call replaces the whole conversation | never reached |
|
|
41
|
+
| **old details** | gone unless the summary kept them | come back the moment a message is about them |
|
|
42
|
+
| **your instructions** | survive if the summary mentions them | kept word for word |
|
|
43
|
+
| **the session** | one window, then you start over | as sharp at turn three hundred as at turn three |
|
|
44
|
+
|
|
45
|
+
## Results
|
|
46
|
+
|
|
47
|
+
Measured on 24 held-out sessions of 90 to 120 turns with 600 facts planted
|
|
48
|
+
at known depths, such as a value a command printed once, an error, an
|
|
49
|
+
instruction that was later revised or a decision with its rejected options,
|
|
50
|
+
and scored by exact match with no judge involved.
|
|
51
|
+
|
|
52
|
+
| | facts remembered | rules followed without a reminder | prompt size when asked | tokens per session | transcript lookups per session | time per session | said "I don't know" | answered wrong |
|
|
53
|
+
|---|---|---|---|---|---|---|---|---|
|
|
54
|
+
| Claude Code's compaction | 70% | 92% | 70k | 741k | 10.1 | 53s | 172 of 600 | 7 of 600 |
|
|
55
|
+
| Claude Code, told a transcript exists | 99% | 92% | 71k | 1,315k | 23.0 | 88s | 3 of 600 | 6 of 600 |
|
|
56
|
+
| keeping the whole history | 100% | 90% | 411k | 815k | 0 | 67s | 0 of 600 | 3 of 600 |
|
|
57
|
+
| eunoe, folding only | 99% | 92% | 118k | 490k | 2.7 | 40s | 4 of 600 | 5 of 600 |
|
|
58
|
+
| **eunoe** | **99%** | **95%** | **110k** | **305k** | **0.1** | **39s** | **1 of 600** | **3 of 600** |
|
|
59
|
+
|
|
60
|
+
*Facts remembered* is how many of the planted facts the agent could still
|
|
61
|
+
answer correctly after the cut. *Rules followed without a reminder* is how
|
|
62
|
+
often it applied an instruction from earlier in the session when the task
|
|
63
|
+
called for it, without being told again. *Prompt size* is the context the
|
|
64
|
+
model was reading when the question came, while *tokens per session* adds up
|
|
65
|
+
every request it took to get through the questions, so it grows whenever the
|
|
66
|
+
agent has to go and look something up.
|
|
67
|
+
|
|
68
|
+
That is **29 points more recall than Claude Code's compaction** (95% CI +17
|
|
69
|
+
to +42), better on 12 sessions and worse on none, with 3 wrong answers out
|
|
70
|
+
of 600, and the same margin holds on 27 cut points taken from real sessions.
|
|
71
|
+
eunoe matches keeping the whole history at a quarter of the context and 40%
|
|
72
|
+
of the tokens.
|
|
73
|
+
|
|
74
|
+
The surprising part is not that eunoe remembers more, it is how. Telling
|
|
75
|
+
Claude Code that a transcript exists gets it to 99% too, but it has to grep
|
|
76
|
+
for the answer 23 times a session to do it, which is why it burns four times
|
|
77
|
+
the tokens. eunoe reaches the same 99% with about one lookup every ten
|
|
78
|
+
sessions, because the answer is already in the prompt when the question
|
|
79
|
+
arrives, and it is faster than keeping the whole history even though that
|
|
80
|
+
arm never searches at all. The two eunoe rows differ only in whether the
|
|
81
|
+
exact lines of old output are kept and brought back, which is the part of
|
|
82
|
+
the design explained below.
|
|
83
|
+
|
|
84
|
+
The method was chosen on a separate development split and frozen before the
|
|
85
|
+
test split ran. The suites, the pre-registered spec, every per-arm table and
|
|
86
|
+
the limits of the study (one developer's repositories, one answering model,
|
|
87
|
+
one run per arm) are in [`bench/`](bench/README.md).
|
|
44
88
|
|
|
45
89
|
## Install
|
|
46
90
|
|
|
47
91
|
```sh
|
|
48
|
-
bun add -g @chroxify/eunoe # puts `eunoe` on your PATH
|
|
49
|
-
eunoe
|
|
92
|
+
bun add -g @chroxify/eunoe # puts `eunoe` and the short `ee` on your PATH
|
|
93
|
+
eunoe start # sets up the background proxy, routes Claude Code through it
|
|
50
94
|
```
|
|
51
95
|
|
|
52
|
-
Then use Claude Code exactly as before. New sessions go through eunoe
|
|
53
|
-
|
|
96
|
+
Then use Claude Code exactly as before. New sessions go through eunoe, and
|
|
97
|
+
sessions that were already running pick it up when you restart them.
|
|
98
|
+
`eunoe stop` turns it off again.
|
|
99
|
+
|
|
100
|
+
Requires [Bun](https://bun.sh). On macOS `start` sets up a launchd agent that
|
|
101
|
+
keeps the proxy alive; on other platforms keep `eunoe serve` running however
|
|
102
|
+
you like and everything else works the same.
|
|
54
103
|
|
|
55
|
-
|
|
56
|
-
other platforms, keep `eunoe serve` running however you like (systemd, a
|
|
57
|
-
terminal) and the rest works the same.
|
|
104
|
+
## How it works
|
|
58
105
|
|
|
59
|
-
|
|
106
|
+
eunoe does three things, and each one was measured on its own before it was
|
|
107
|
+
kept.
|
|
60
108
|
|
|
61
|
-
|
|
62
|
-
upstream byte for byte as Claude Code built it.
|
|
109
|
+
### Folding
|
|
63
110
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
111
|
+
A **turn** is one message from you and everything the agent did until your
|
|
112
|
+
next one. Once a turn is finished, most of what it holds is tool output the
|
|
113
|
+
agent needed in the moment and will never read again.
|
|
114
|
+
|
|
115
|
+
A finished turn, as Claude Code keeps it (about 9,000 tokens):
|
|
67
116
|
|
|
68
117
|
```
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
last words + its tool calls]
|
|
86
|
-
turn 39 your message → final reply
|
|
87
|
-
turn 40 your message + every tool call
|
|
88
|
-
and result, exactly as it was
|
|
89
|
-
turn 41+ grows normally until the next cut
|
|
118
|
+
[you] fix the failing lease test
|
|
119
|
+
|
|
120
|
+
[agent] I'll look at the test first.
|
|
121
|
+
[tool] Read src/lease.test.ts
|
|
122
|
+
... 140 lines of file ...
|
|
123
|
+
[tool] Bash bun test
|
|
124
|
+
... 80 lines of output ...
|
|
125
|
+
|
|
126
|
+
[agent] The expiry check is off by one.
|
|
127
|
+
[tool] Edit src/lease.ts
|
|
128
|
+
[tool] Bash bun test
|
|
129
|
+
... 80 lines of output ...
|
|
130
|
+
|
|
131
|
+
[agent] Fixed the off-by-one in renew(): the expiry check used <= instead
|
|
132
|
+
of <. All 4 tests pass. The 6011 code comes from LEASE_EXPIRED in
|
|
133
|
+
src/errors.ts.
|
|
90
134
|
```
|
|
91
135
|
|
|
92
|
-
|
|
93
|
-
a hundred turns is ~60k. There is no reason to throw them away. This is what
|
|
94
|
-
keeps *"what did we do about the Stripe thing"* and *"you told me to deploy it
|
|
95
|
-
yourself"* answerable after a cut.
|
|
136
|
+
The same turn after eunoe folds it (about 700 tokens):
|
|
96
137
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
instructions. Its hash also travels in a billing header, so eunoe never edits it.
|
|
138
|
+
```
|
|
139
|
+
[you] fix the failing lease test
|
|
100
140
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
141
|
+
[evidence] Bash bun test
|
|
142
|
+
FAIL lease renews after expiry
|
|
143
|
+
expected 6011, got undefined
|
|
144
|
+
3 pass, 1 fail
|
|
105
145
|
|
|
106
|
-
|
|
146
|
+
[agent] Fixed the off-by-one in renew(): the expiry check used <= instead
|
|
147
|
+
of <. All 4 tests pass. The 6011 code comes from LEASE_EXPIRED in
|
|
148
|
+
src/errors.ts.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The agent already writes a usable summary of every turn in its final reply,
|
|
152
|
+
so folding keeps that reply, your message and the evidence described below,
|
|
153
|
+
and drops everything else. A hundred folded turns come to about 70k tokens,
|
|
154
|
+
which is why eunoe never has to throw a turn away.
|
|
155
|
+
|
|
156
|
+
The timing is what makes this cheap. The last 3 turns are always sent whole,
|
|
157
|
+
down to the last tool call, and older turns stay whole too until the detail
|
|
158
|
+
in them passes a budget of 100k tokens. At that point every turn except
|
|
159
|
+
those 3 folds in one step.
|
|
107
160
|
|
|
108
161
|
```
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
162
|
+
turn 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
|
163
|
+
-----------------------------------------------------------------------
|
|
164
|
+
t=10 W W W W W W W W W L budget not reached
|
|
165
|
+
t=20 W W W W W W W W W W W W W W W W W W W L
|
|
166
|
+
t=21 F F F F F F F F F F F F F F F F F F W W L <- fold
|
|
167
|
+
t=24 F F F F F F F F F F F F F F F F F F W W W W W L
|
|
168
|
+
|
|
169
|
+
W whole F folded L the live turn
|
|
113
170
|
```
|
|
114
171
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
172
|
+
It happens in a step rather than a slide because of the prompt cache. The
|
|
173
|
+
cache is a prefix match, so changing anything in the middle rewrites
|
|
174
|
+
everything after it at 1.25 times the price of a read. Folding one turn per
|
|
175
|
+
message would rewrite the cache on every message, whereas folding a budget
|
|
176
|
+
at a time writes the new prefix once and lets every request until the next
|
|
177
|
+
step read it at a tenth of the price.
|
|
118
178
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
179
|
+
The first message is always kept verbatim, because Claude Code packs
|
|
180
|
+
`CLAUDE.md`, your git status and your skills into it and its hash travels in
|
|
181
|
+
a billing header. The live turns are untouched too, thinking blocks and
|
|
182
|
+
interruptions included. If a turn was interrupted before the agent replied,
|
|
183
|
+
the fold keeps its last words and the list of what it ran instead.
|
|
122
184
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
| mode | before the limit | at the limit |
|
|
126
|
-
|---|---|---|
|
|
127
|
-
| **`compact`** (default) | everything, unchanged | the cut, above |
|
|
128
|
-
| **`trim`** | every finished turn is already just your message + the final reply | rarely reached |
|
|
129
|
-
| **`off`** | passthrough | Claude Code's own compaction |
|
|
185
|
+
### Evidence
|
|
130
186
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
187
|
+
Folding on its own would lose the exact values in tool output, like the
|
|
188
|
+
deployment id, the error code or the test count, so when a turn folds each
|
|
189
|
+
command's output is scored line by line and the lines worth keeping are
|
|
190
|
+
quoted verbatim under the command that produced them.
|
|
135
191
|
|
|
136
|
-
```
|
|
137
|
-
|
|
192
|
+
```
|
|
193
|
+
score line
|
|
194
|
+
----- ------------------------------------------------------------
|
|
195
|
+
+3 FAIL lease renews after expiry error words and codes
|
|
196
|
+
+3 Deployment ID: dpl_qgthe6u4p7 ids, hashes, URLs
|
|
197
|
+
+2 3 pass, 1 fail test and diff summaries
|
|
198
|
+
+2 ERR_LEASE_6011: lease expired error codes
|
|
199
|
+
+2 took 412 ms durations, sizes, versions
|
|
200
|
+
+1 status: applied key: value
|
|
201
|
+
0 Compiling 14 modules... dropped
|
|
202
|
+
0 > lease@1.4.0 test dropped
|
|
138
203
|
```
|
|
139
204
|
|
|
140
|
-
|
|
141
|
-
|
|
205
|
+
Every command keeps its best lines, and the highest scorers across the turn
|
|
206
|
+
fill a budget of about 4,000 characters, which is a few hundred tokens. The
|
|
207
|
+
cap exists because a long test or build log would otherwise fold to
|
|
208
|
+
something bigger than the turn it came from, and the size was chosen on the
|
|
209
|
+
development split, where it kept 97% of the planted values. Commands whose
|
|
210
|
+
output can simply be run again, such as `Read`, `Grep`, `Glob`, `Edit` and
|
|
211
|
+
`Write`, are skipped entirely.
|
|
142
212
|
|
|
143
|
-
|
|
213
|
+
### Recall
|
|
144
214
|
|
|
145
|
-
|
|
146
|
-
|
|
215
|
+
Each time you send a message, before the agent responds, eunoe uses it as a
|
|
216
|
+
search query over every tool output that is no longer in context and
|
|
217
|
+
everything the agent said while working. Matching excerpts are quoted
|
|
218
|
+
exactly and appended to your message, oldest first.
|
|
147
219
|
|
|
148
|
-
|
|
149
|
-
|
|
220
|
+
```
|
|
221
|
+
you (turn 71) deploy is failing with ERR_LEASE_6011 again
|
|
150
222
|
|
|
151
|
-
|
|
223
|
+
eunoe appends:
|
|
152
224
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
225
|
+
<recalled-context>
|
|
226
|
+
[turn 12] Bash `bun run deploy:staging`
|
|
227
|
+
Deployment ID: dpl_qgthe6u4p7
|
|
228
|
+
ERR_LEASE_6011: lease expired, retrying with --fresh
|
|
229
|
+
</recalled-context>
|
|
230
|
+
```
|
|
158
231
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
232
|
+
Candidates come from BM25, the keyword ranking that search engines use,
|
|
233
|
+
which takes about 6 ms and no model. Optionally [Jev](https://typesafe.ai),
|
|
234
|
+
a small and very fast model, reorders the top 30 in about 300 ms, which
|
|
235
|
+
lifted the right excerpt from 88% to 95% of questions and beat every other
|
|
236
|
+
method and model that was tested. Without a key the BM25 order is used and
|
|
237
|
+
nothing else changes. The recall is computed once per message and pinned to
|
|
238
|
+
it, so it never disturbs the cache.
|
|
162
239
|
|
|
163
|
-
|
|
164
|
-
thing it carries forward, and what belongs in it.
|
|
240
|
+
## How it plugs in
|
|
165
241
|
|
|
166
|
-
|
|
242
|
+
```
|
|
243
|
+
Claude Code ------> eunoe ------> api.anthropic.com
|
|
244
|
+
CLI, desktop app, 127.0.0.1:8788
|
|
245
|
+
Agent SDK rewrites /v1/messages
|
|
246
|
+
passes everything else through
|
|
247
|
+
```
|
|
167
248
|
|
|
168
|
-
|
|
169
|
-
|
|
249
|
+
Claude Code honours `ANTHROPIC_BASE_URL` from its `settings.json`, so every
|
|
250
|
+
harness built on it works with no code changes, and because auth headers are
|
|
251
|
+
forwarded untouched your existing login keeps working whether it is a
|
|
252
|
+
subscription or an API key.
|
|
170
253
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
| **Typing while the agent works** | Claude Code sends a mid-conversation `system` message. It stays inside the running turn; in a reduced turn it is merged in right before the final reply. |
|
|
175
|
-
| **An interrupted turn gets reduced** | No final reply exists, so eunoe writes one: `[This turn was interrupted before a final reply.] Last thing you said: … Tool calls (12): Edit src/app.ts; Bash \`bun test\`; …` |
|
|
176
|
-
| **The cut lands on an interrupted turn** | Kept verbatim. The agent sees exactly where it stopped. |
|
|
177
|
-
| **Esc-Esc rewind past the cut** | eunoe notices its cut turn is gone from the history and starts over from whatever Claude Code sends. |
|
|
254
|
+
Claude Code still keeps the whole conversation on disk at
|
|
255
|
+
`~/.claude/projects/<project>/<session>.jsonl`, since eunoe only changes
|
|
256
|
+
what goes out on the wire.
|
|
178
257
|
|
|
179
|
-
##
|
|
258
|
+
## The transcript
|
|
180
259
|
|
|
181
|
-
Nothing is lost,
|
|
182
|
-
|
|
260
|
+
Nothing is ever lost, because eunoe keeps a readable copy of the whole
|
|
261
|
+
session and teaches the agent to use it. The session file is rendered into a
|
|
262
|
+
folder of markdown under `~/.eunoe/transcripts/<session>/`, with one file
|
|
263
|
+
per turn and an index.
|
|
183
264
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
265
|
+
```
|
|
266
|
+
index.md one line per turn: what you asked and how the turn ended
|
|
267
|
+
turn-0001.md ## User your message
|
|
268
|
+
turn-0002.md ### [tool 3/12] every tool call, with its full output below it
|
|
269
|
+
... ## Final reply the agent's reply
|
|
270
|
+
```
|
|
190
271
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
272
|
+
Markdown is far easier for an agent to grep and read than Claude Code's raw
|
|
273
|
+
JSONL, and the folder is rebuilt as the session grows. The agent is given the
|
|
274
|
+
path and a short recipe for searching it, so when it needs an exact detail
|
|
275
|
+
that neither evidence nor recall surfaced, it can find it in a couple of
|
|
276
|
+
commands instead of guessing. In the benchmark that happened about once
|
|
277
|
+
every ten sessions.
|
|
194
278
|
|
|
195
279
|
## Prompt caching
|
|
196
280
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
a reduced turn always reduces identically, and nothing time-based is ever
|
|
203
|
-
inserted. The prefix changes only at a cut.
|
|
281
|
+
Cache writes cost 1.25 times the input price and reads a tenth of it, so a
|
|
282
|
+
context scheme that breaks the cache costs more than it saves. eunoe's
|
|
283
|
+
output is deterministic, which means the same history always produces the
|
|
284
|
+
same bytes, recall is pinned to the message it was computed for and nothing
|
|
285
|
+
time-based is ever inserted, so the prefix only ever changes at a fold.
|
|
204
286
|
|
|
205
287
|
```
|
|
206
|
-
turns
|
|
207
|
-
turn
|
|
208
|
-
turns
|
|
288
|
+
turns 1-20 byte-identical to no proxy at all -> cache hits as usual
|
|
289
|
+
turn 21 fold: one new prefix, written once -> one cache write
|
|
290
|
+
turns 22-58 same prefix, new turns appended -> reads hit again
|
|
291
|
+
turn 59 next fold
|
|
209
292
|
```
|
|
210
293
|
|
|
211
|
-
Measured through the proxy
|
|
212
|
-
1.5k written
|
|
213
|
-
context into a summary call and
|
|
214
|
-
|
|
215
|
-
## Edge cases
|
|
294
|
+
Measured through the proxy right after a fold, 180k tokens came from the
|
|
295
|
+
cache and 1.5k were written. Claude Code's own compaction reads the entire
|
|
296
|
+
context into a summary call and then writes a new prefix anyway.
|
|
216
297
|
|
|
217
|
-
|
|
298
|
+
## Modes
|
|
218
299
|
|
|
219
|
-
|
|
|
300
|
+
| mode | |
|
|
220
301
|
|---|---|
|
|
221
|
-
|
|
|
222
|
-
|
|
|
223
|
-
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
## Early results
|
|
234
|
-
|
|
235
|
-
A full, pre-registered benchmark is in progress and will be published with its
|
|
236
|
-
harness, raw data and every per-point result. Until then, here is what an
|
|
237
|
-
earlier internal run showed, with its limits stated.
|
|
238
|
-
|
|
239
|
-
**Setup.** 45 cut points across 14 projects, from one developer's real Claude
|
|
240
|
-
Code history: real auto-compactions, plus long sessions cut where they crossed
|
|
241
|
-
350k tokens. At each point, every strategy answered the same 10 questions about
|
|
242
|
-
the session — recent details, what was worked on 5–15 turns earlier, standing
|
|
243
|
-
instructions, decisions, and what was in progress — through a real Claude Code
|
|
244
|
-
session, with read-only access to a transcript that stopped at the cut. Answers
|
|
245
|
-
were judged blind.
|
|
246
|
-
|
|
247
|
-
| strategy | score | context after the cut | vs native, 95% CI |
|
|
248
|
-
|---|---|---|---|
|
|
249
|
-
| Claude Code's compaction | 84% | 82k | — |
|
|
250
|
-
| **eunoe** | **95%** | 183k | **+11.0%** [+8.1, +13.9] |
|
|
251
|
-
| eunoe, `trim` mode | 95% | 179k | +11.6% [+8.8, +14.4] |
|
|
252
|
-
| eunoe, last 5 turns only | 90% | 164k | +6.4% [+2.4, +10.1] |
|
|
253
|
-
| no compaction at all (ceiling) | 97% | 348k | — (7 points) |
|
|
254
|
-
|
|
255
|
-
eunoe came within two points of never compacting at all, at about half the
|
|
256
|
-
context. Claude Code's compaction lost most on what was worked on mid-session
|
|
257
|
-
(74%), decisions (77%) and what was in progress (77%).
|
|
258
|
-
|
|
259
|
-
**What this does not show yet.** The run had no budget-matched baseline — a plain
|
|
260
|
-
sliding window given the same token budget — so it can't separate "eunoe keeps
|
|
261
|
-
the right things" from "eunoe keeps more". It also used one judge, and many of its
|
|
262
|
-
questions were easy enough that every strategy passed them. The full benchmark
|
|
263
|
-
adds that baseline, a second judge, harder questions vetted before scoring, and a
|
|
264
|
-
task-continuation test, and reports whatever it finds.
|
|
302
|
+
| **`default`** | everything above, recommended |
|
|
303
|
+
| **`compact`** | only the compaction is replaced: the context grows as it normally would, and at the limit eunoe folds instead of summarising, so you get the recall but none of the savings along the way |
|
|
304
|
+
| **`off`** | passthrough, Claude Code compacts on its own |
|
|
305
|
+
|
|
306
|
+
```sh
|
|
307
|
+
eunoe mode # pick one
|
|
308
|
+
eunoe mode compact # or name it
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Whichever mode is on owns compaction, so eunoe disables Claude Code's
|
|
312
|
+
auto-compact while it is active and hands it back on `off` or `stop`.
|
|
265
313
|
|
|
266
314
|
## Usage
|
|
267
315
|
|
|
268
316
|
```sh
|
|
269
|
-
eunoe
|
|
270
|
-
eunoe
|
|
271
|
-
eunoe
|
|
272
|
-
eunoe
|
|
273
|
-
eunoe
|
|
274
|
-
eunoe
|
|
317
|
+
eunoe start # route Claude Code through eunoe (sets up the proxy the first time)
|
|
318
|
+
eunoe stop # new sessions bypass eunoe; running ones keep working
|
|
319
|
+
eunoe mode # pick default | compact | off
|
|
320
|
+
eunoe settings # pick a setting and change it
|
|
321
|
+
eunoe settings list # show them all
|
|
322
|
+
eunoe settings set keep 5 # change one directly
|
|
323
|
+
eunoe status # what's running, every session, context and tokens saved
|
|
324
|
+
eunoe uninstall # stop and remove the background proxy
|
|
275
325
|
```
|
|
276
326
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
{
|
|
281
|
-
"mode": "compact",
|
|
282
|
-
"compactAt": 0.9,
|
|
283
|
-
"keepTurns": "all",
|
|
284
|
-
"search": "markdown",
|
|
285
|
-
"port": 8788,
|
|
286
|
-
"claudeConfigDirs": []
|
|
287
|
-
}
|
|
288
|
-
```
|
|
327
|
+
`ee` is an alias, so `ee status` and `eunoe status` are the same command.
|
|
328
|
+
|
|
329
|
+
### Per session
|
|
289
330
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
331
|
+
Everything above takes `--session <id>` to apply to one session only. Session
|
|
332
|
+
ids are in Claude Code's `/status`, `eunoe status` lists the ones it has seen
|
|
333
|
+
with their working directory, and a unique prefix is enough.
|
|
293
334
|
|
|
294
335
|
```sh
|
|
295
|
-
eunoe
|
|
336
|
+
eunoe start --session 1baada86 # manage only this session, pass the rest through untouched
|
|
337
|
+
eunoe mode compact --session 1baada86
|
|
338
|
+
eunoe settings set keep 5 --session 1baada86
|
|
339
|
+
eunoe settings set keep default --session 1baada86 # back to the shared value
|
|
340
|
+
eunoe stop --session 1baada86
|
|
296
341
|
```
|
|
297
342
|
|
|
298
|
-
|
|
343
|
+
Sessions that aren't managed still go through the proxy, so any of them can
|
|
344
|
+
be switched on later without a restart, and every change applies from the
|
|
345
|
+
session's next request.
|
|
299
346
|
|
|
300
|
-
|
|
301
|
-
around 93%, so eunoe goes slightly earlier at 90% to win the race.
|
|
347
|
+
### Settings
|
|
302
348
|
|
|
303
|
-
|
|
304
|
-
|
|
349
|
+
| key | default | what it does |
|
|
350
|
+
|---|---|---|
|
|
351
|
+
| `keep` | `3` | turns kept whole at the end |
|
|
352
|
+
| `budget` | `100000` | tokens of older detail that trigger a fold |
|
|
353
|
+
| `compactAt` | `0.9` | share of the context window that triggers the full cut |
|
|
354
|
+
| `keepTurns` | `all` | folded turns kept after a full cut |
|
|
355
|
+
| `search` | `markdown` | transcript form for the agent: `markdown`, `xml`, `jsonl` or `qmd` (semantic, via [qmd](https://github.com/tobi/qmd)) |
|
|
356
|
+
| `rerank` | `jev-preview` | Typesafe model that reorders recall, or `off` |
|
|
357
|
+
|
|
358
|
+
`~/.eunoe/config.json` holds the same keys plus `port`, `claudeConfigDirs`
|
|
359
|
+
and `sessions`. Tools that run Claude Code from their own config directory
|
|
360
|
+
need eunoe to know about it, which `eunoe start --config-dir <dir>` does
|
|
361
|
+
once and remembers.
|
|
362
|
+
|
|
363
|
+
### Better recall with Jev
|
|
364
|
+
|
|
365
|
+
Recall works out of the box, but it gets noticeably better with
|
|
366
|
+
[Jev](https://typesafe.ai) reordering the candidates. To turn that on, put a
|
|
367
|
+
Typesafe API key in `~/.eunoe/typesafe-key` or in the `TYPESAFE_API_KEY`
|
|
368
|
+
environment variable, and `eunoe status` will show the reranker as active.
|
|
369
|
+
It can't slow anything down, because if Jev errors or takes longer than four
|
|
370
|
+
seconds the recall simply uses the plain keyword order instead. To switch it
|
|
371
|
+
off, run `eunoe settings set rerank off`.
|
|
372
|
+
|
|
373
|
+
### Logs
|
|
374
|
+
|
|
375
|
+
Every request is written to `~/.eunoe/requests.jsonl` with what eunoe did
|
|
376
|
+
to it and how much of it the API served from cache, which is also where
|
|
377
|
+
`eunoe status` gets its numbers.
|
|
305
378
|
|
|
306
379
|
## What it doesn't do
|
|
307
380
|
|
|
308
|
-
- **It is a proxy,
|
|
309
|
-
`ANTHROPIC_BASE_URL` still points at it, every Claude request
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
381
|
+
- **It is a proxy, which makes it a single point of failure.** If it isn't
|
|
382
|
+
running while `ANTHROPIC_BASE_URL` still points at it, every Claude request
|
|
383
|
+
fails. launchd keeps it alive, and `stop` leaves it running so sessions
|
|
384
|
+
already pointed at it keep working.
|
|
385
|
+
- **It only understands Claude Code's shape of conversation.** Other
|
|
386
|
+
Anthropic API clients pass through unchanged and get nothing out of it.
|
|
387
|
+
- **It can't recover what Claude Code already compacted,** so turn it on
|
|
388
|
+
before a long session rather than after.
|
|
389
|
+
- **Recall is keyword search.** Ask about "the deploy that failed" and it
|
|
390
|
+
finds the deploy output, but ask about "that thing from before" and it
|
|
391
|
+
finds nothing, leaving the agent to search the transcript itself. Jev
|
|
392
|
+
narrows this gap without closing it.
|
|
393
|
+
- **It never cuts against a limit it can't verify.** The context window size
|
|
394
|
+
comes from the Models API for each model, and if that lookup fails the
|
|
395
|
+
budget folds still happen but the full cut does not.
|
|
396
|
+
|
|
397
|
+
## Credits
|
|
398
|
+
|
|
399
|
+
- [qmd](https://github.com/tobi/qmd) by Tobi Lütke provides the local
|
|
400
|
+
semantic search behind the `qmd` setting.
|
|
401
|
+
- [Jev](https://typesafe.ai/blog/introducing-system-one-models-and-jev) by
|
|
402
|
+
Typesafe is the system-one model that reranks recall.
|
|
325
403
|
|
|
326
404
|
## License
|
|
327
405
|
|
|
328
|
-
MIT
|
|
406
|
+
eunoe is licensed under the [MIT License](LICENSE), and welcomes
|
|
407
|
+
contributions and suggestions.
|