@visual-z/xeno 0.1.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.
Files changed (180) hide show
  1. package/README.md +498 -0
  2. package/docs/install.md +130 -0
  3. package/package.json +43 -0
  4. package/src/apps/approve.ts +49 -0
  5. package/src/apps/cli.ts +37 -0
  6. package/src/apps/commands.ts +226 -0
  7. package/src/apps/completions.ts +28 -0
  8. package/src/apps/input.ts +297 -0
  9. package/src/apps/render.ts +207 -0
  10. package/src/apps/shell.ts +2353 -0
  11. package/src/apps/startup.ts +1302 -0
  12. package/src/core/benchmark.ts +195 -0
  13. package/src/core/boundary.ts +174 -0
  14. package/src/core/churn.ts +59 -0
  15. package/src/core/completion-review.ts +66 -0
  16. package/src/core/context-policy.ts +60 -0
  17. package/src/core/context-window.ts +54 -0
  18. package/src/core/continuation.ts +114 -0
  19. package/src/core/cycle.ts +17 -0
  20. package/src/core/data-query.ts +201 -0
  21. package/src/core/deadline.ts +39 -0
  22. package/src/core/derive.ts +489 -0
  23. package/src/core/edit-plan.ts +200 -0
  24. package/src/core/environment.ts +29 -0
  25. package/src/core/event-validation.ts +99 -0
  26. package/src/core/events.ts +486 -0
  27. package/src/core/evidence-links.ts +71 -0
  28. package/src/core/evidence.ts +89 -0
  29. package/src/core/execution.ts +12 -0
  30. package/src/core/exhaustive.ts +18 -0
  31. package/src/core/experiment.ts +40 -0
  32. package/src/core/export.ts +169 -0
  33. package/src/core/file-references.ts +35 -0
  34. package/src/core/frontmatter.ts +213 -0
  35. package/src/core/handoff.ts +397 -0
  36. package/src/core/harness-comparison.ts +90 -0
  37. package/src/core/history-query.ts +103 -0
  38. package/src/core/history.ts +100 -0
  39. package/src/core/json-event.ts +135 -0
  40. package/src/core/json-pointer.ts +46 -0
  41. package/src/core/keymap.ts +241 -0
  42. package/src/core/observation-view.ts +87 -0
  43. package/src/core/outcome.ts +45 -0
  44. package/src/core/overflow.ts +59 -0
  45. package/src/core/permissions.ts +182 -0
  46. package/src/core/policy-evaluation.ts +55 -0
  47. package/src/core/progress.ts +101 -0
  48. package/src/core/project.ts +372 -0
  49. package/src/core/prompts.ts +83 -0
  50. package/src/core/queue.ts +60 -0
  51. package/src/core/read-bounds.ts +15 -0
  52. package/src/core/recall-analysis.ts +90 -0
  53. package/src/core/recall-trial.ts +153 -0
  54. package/src/core/response-recovery.ts +46 -0
  55. package/src/core/scheduling.ts +8 -0
  56. package/src/core/settings.ts +174 -0
  57. package/src/core/skills.ts +121 -0
  58. package/src/core/stall.ts +48 -0
  59. package/src/core/text-normalize.ts +81 -0
  60. package/src/core/theme.ts +179 -0
  61. package/src/core/tokens.ts +149 -0
  62. package/src/core/tool-contract.ts +102 -0
  63. package/src/core/tool-input.ts +38 -0
  64. package/src/core/transcript.ts +60 -0
  65. package/src/core/window-trial.ts +65 -0
  66. package/src/core/work-state.ts +103 -0
  67. package/src/core/yaml.ts +282 -0
  68. package/src/index.ts +10 -0
  69. package/src/runtime/auth.ts +255 -0
  70. package/src/runtime/benchmark.ts +187 -0
  71. package/src/runtime/catalog.ts +365 -0
  72. package/src/runtime/evidence-notes.ts +33 -0
  73. package/src/runtime/exec-sessions.ts +214 -0
  74. package/src/runtime/execution-control.ts +39 -0
  75. package/src/runtime/harness-comparison.ts +356 -0
  76. package/src/runtime/history-tools.ts +108 -0
  77. package/src/runtime/loop.ts +1983 -0
  78. package/src/runtime/model.ts +614 -0
  79. package/src/runtime/models-fetch.ts +155 -0
  80. package/src/runtime/oauth-device.ts +86 -0
  81. package/src/runtime/oauth.ts +759 -0
  82. package/src/runtime/openai-responses.ts +286 -0
  83. package/src/runtime/openai.ts +675 -0
  84. package/src/runtime/provider.ts +498 -0
  85. package/src/runtime/recall-trial.ts +226 -0
  86. package/src/runtime/resume.ts +66 -0
  87. package/src/runtime/retry.ts +297 -0
  88. package/src/runtime/tools.ts +1193 -0
  89. package/src/runtime/usage.ts +225 -0
  90. package/src/runtime/window-trial.ts +140 -0
  91. package/src/runtime/working-notes.ts +30 -0
  92. package/src/store/atomic.ts +31 -0
  93. package/src/store/auth-store.ts +50 -0
  94. package/src/store/blobs.ts +142 -0
  95. package/src/store/command.ts +75 -0
  96. package/src/store/config.ts +69 -0
  97. package/src/store/context-files.ts +356 -0
  98. package/src/store/file-index.ts +81 -0
  99. package/src/store/git-branch.ts +35 -0
  100. package/src/store/history-search.ts +19 -0
  101. package/src/store/log.ts +642 -0
  102. package/src/store/models.ts +41 -0
  103. package/src/store/observations.ts +181 -0
  104. package/src/store/query-source.ts +44 -0
  105. package/src/store/recovery.ts +39 -0
  106. package/src/store/replay.ts +63 -0
  107. package/src/store/resources.ts +361 -0
  108. package/src/store/restore.ts +302 -0
  109. package/src/store/search.ts +644 -0
  110. package/src/store/serial.ts +13 -0
  111. package/src/store/sessions.ts +463 -0
  112. package/src/store/trust.ts +69 -0
  113. package/src/store/workspace.ts +256 -0
  114. package/src/store/worktree.ts +233 -0
  115. package/src/tui/ansi.ts +67 -0
  116. package/src/tui/autocomplete.ts +29 -0
  117. package/src/tui/complete.ts +87 -0
  118. package/src/tui/diff.ts +160 -0
  119. package/src/tui/editor.ts +893 -0
  120. package/src/tui/exhaustive.ts +14 -0
  121. package/src/tui/fuzzy.ts +75 -0
  122. package/src/tui/host.ts +223 -0
  123. package/src/tui/input.ts +69 -0
  124. package/src/tui/keys.ts +378 -0
  125. package/src/tui/markdown.ts +17 -0
  126. package/src/tui/render.ts +153 -0
  127. package/src/tui/select.ts +240 -0
  128. package/src/tui/settings.ts +40 -0
  129. package/src/tui/statusline.ts +162 -0
  130. package/src/tui/style.ts +205 -0
  131. package/src/tui/terminal.ts +30 -0
  132. package/src/tui/text.ts +52 -0
  133. package/src/tui/toolview.ts +386 -0
  134. package/src/tui/transcript.ts +71 -0
  135. package/src/tui/tree.ts +405 -0
  136. package/src/tui/width.ts +567 -0
  137. package/src/vendor/pi-tui/LICENSE.upstream +21 -0
  138. package/src/vendor/pi-tui/VENDOR.md +23 -0
  139. package/src/vendor/pi-tui/alt-screen-search.ts +327 -0
  140. package/src/vendor/pi-tui/autocomplete.ts +835 -0
  141. package/src/vendor/pi-tui/components/alt-screen-flash.ts +51 -0
  142. package/src/vendor/pi-tui/components/box.ts +167 -0
  143. package/src/vendor/pi-tui/components/cancellable-loader.ts +40 -0
  144. package/src/vendor/pi-tui/components/editor.ts +2470 -0
  145. package/src/vendor/pi-tui/components/h-stack.ts +44 -0
  146. package/src/vendor/pi-tui/components/image.ts +127 -0
  147. package/src/vendor/pi-tui/components/input.ts +494 -0
  148. package/src/vendor/pi-tui/components/loader.ts +101 -0
  149. package/src/vendor/pi-tui/components/markdown.ts +1015 -0
  150. package/src/vendor/pi-tui/components/mouse-region.ts +33 -0
  151. package/src/vendor/pi-tui/components/scroll-view.ts +224 -0
  152. package/src/vendor/pi-tui/components/select-list.ts +273 -0
  153. package/src/vendor/pi-tui/components/settings-list.ts +328 -0
  154. package/src/vendor/pi-tui/components/spacer.ts +28 -0
  155. package/src/vendor/pi-tui/components/stack.ts +154 -0
  156. package/src/vendor/pi-tui/components/text.ts +107 -0
  157. package/src/vendor/pi-tui/components/truncated-text.ts +65 -0
  158. package/src/vendor/pi-tui/components/v-stack.ts +33 -0
  159. package/src/vendor/pi-tui/editor-component.ts +74 -0
  160. package/src/vendor/pi-tui/fuzzy.ts +138 -0
  161. package/src/vendor/pi-tui/index.ts +156 -0
  162. package/src/vendor/pi-tui/keybindings.ts +320 -0
  163. package/src/vendor/pi-tui/keys.ts +1401 -0
  164. package/src/vendor/pi-tui/kill-ring.ts +46 -0
  165. package/src/vendor/pi-tui/latex.ts +1506 -0
  166. package/src/vendor/pi-tui/layout-node.ts +51 -0
  167. package/src/vendor/pi-tui/layout.ts +449 -0
  168. package/src/vendor/pi-tui/native-modifiers.ts +13 -0
  169. package/src/vendor/pi-tui/native-module-path.ts +31 -0
  170. package/src/vendor/pi-tui/native-platform.ts +63 -0
  171. package/src/vendor/pi-tui/stdin-buffer.ts +444 -0
  172. package/src/vendor/pi-tui/terminal-colors.ts +73 -0
  173. package/src/vendor/pi-tui/terminal-image.ts +696 -0
  174. package/src/vendor/pi-tui/terminal.ts +547 -0
  175. package/src/vendor/pi-tui/tui-alt-screen.ts +1742 -0
  176. package/src/vendor/pi-tui/tui-main-screen.ts +655 -0
  177. package/src/vendor/pi-tui/tui.ts +1456 -0
  178. package/src/vendor/pi-tui/undo-stack.ts +28 -0
  179. package/src/vendor/pi-tui/utils.ts +1345 -0
  180. package/src/vendor/pi-tui/word-navigation.ts +117 -0
package/README.md ADDED
@@ -0,0 +1,498 @@
1
+ # xeno
2
+
3
+ A segmented append-only agent log with **pure** context and handoff projections.
4
+
5
+ The log is the truth. What the model sees is a recomputable projection of it. Nothing
6
+ is ever summarized by a model, and nothing is ever rewritten in place.
7
+
8
+ ```
9
+ .xeno/sessions/<id>/node-0007.jsonl append-only events, references only
10
+ |
11
+ | deriveHandoff() pure, zero LLM calls
12
+ v
13
+ .xeno/sessions/<id>/handoff-0007.yaml structured state: verified completions,
14
+ | decisions, pitfalls, file digests, gate status
15
+ | projectContext() pure
16
+ v
17
+ messages[] frozen system prefix + handoff + this node's events
18
+ ```
19
+
20
+ Version **0.1.0**. The on-disk session format will still change; `0.x` is that
21
+ statement. Published as `@visual-z/xeno`; the license is `UNLICENSED` (readable and installable, no reuse granted).
22
+
23
+ ## Install
24
+
25
+ Bun is required. This tree was run on Bun 1.3.10. Node has not been tried.
26
+
27
+ ```bash
28
+ npm i -g @visual-z/xeno # or: bun add -g @visual-z/xeno
29
+ xeno --help
30
+ ```
31
+
32
+ From a checkout instead:
33
+
34
+ ```bash
35
+ git clone <this-repo> xeno
36
+ cd xeno
37
+ bun install
38
+ bun src/apps/cli.ts --help
39
+ # or: bun link then, anywhere: xeno --help
40
+ ```
41
+
42
+ Full steps, credential variables, and what `.xeno/` writes: [`docs/install.md`](docs/install.md).
43
+
44
+ Validation commands and current evidence are recorded in
45
+ [`docs/research/harness-practice-rollout-2026-09-23.md`](docs/research/harness-practice-rollout-2026-09-23.md).
46
+
47
+ ## Harness capabilities
48
+
49
+ - One live execution per session, with cancellation, draining, and explicit completion/stop/failure outcomes.
50
+ - Stable work IDs with explicit unblock, reopen, and cancel operations. Verified completion still requires fresh executed evidence.
51
+ - Shared schema validation and structured tool feedback. Reads return file versions; edits support an expected version and return a diff.
52
+ - Writing branches start from the parent's working copy, including uncommitted files, in an isolated Git worktree. Adopt continues that checkout.
53
+ - Independent readers and read-only branches run in a bounded window; writes and state changes are barriers. Results remain in request order.
54
+ - `note(kind="pin", work_id, event, what)` links original observations; `supersedes` replaces an earlier reference. `history(work_id=...)`, `history(resource=...)`, and `history(events=[...])` locate or batch-read the originals.
55
+
56
+ Use `/new` or `--fresh` to start with the current tool protocol. Existing sessions
57
+ keep their saved frozen schemas and concurrency policy. New sessions default to
58
+ three concurrent readers; programmatic callers can set `SessionConfig.maxParallelTools`.
59
+ This setting is not a CLI flag. Evidence freshness reflects the last logged observation.
60
+
61
+ The public-data evaluation protocol, architectural hypotheses, and measured
62
+ results are in [`docs/benchmarks.md`](docs/benchmarks.md). The LongBench v2 pilot
63
+ compares segmentation, full history, and observation masking with the same model;
64
+ its tool-based input protocol is explicitly separate from the official leaderboard.
65
+ The follow-up trial compares the enhanced working-state protocol and computation
66
+ tools with a control on six previously unused questions. Real process-kill recovery
67
+ checks are reported separately from model accuracy.
68
+ The explicit-policy follow-up separates segmented, continuous and adaptive context
69
+ views. Its budget-limited result and negative cases are recorded in the same report;
70
+ neither segmentation nor the experimental adaptive rule has established a quality advantage.
71
+ `bun run scripts/compare-harness.ts smoke --output /absolute/new/smoke-directory`
72
+ also exercises xeno-windowed, xeno-continuous and an installed Pi CLI with shared
73
+ confined tools, without paid model calls. Pi runs on Node through an isolated local
74
+ tool bridge; no Pi SDK is added to the project. Its `plan` / `run` protocol freezes
75
+ source, model, task order and spending policy, including summary calls. An explicitly
76
+ authorized `--budget unlimited` on both `plan` and `run` records costs without a local
77
+ dollar ceiling; upstream errors stop the trial, with HTTP 402 marked as awaiting top-up.
78
+ This checks a controlled configuration, not stock Pi or an official leaderboard.
79
+
80
+ ## Usage
81
+
82
+ ```
83
+ xeno [-m <model>] [--provider <name>] [--api-key <key>]
84
+ [-w <workspace>] [--log <dir>] [--max-turns N]
85
+ [--trigger N --hard N] [--list-models] [--fresh]
86
+ [models --refresh]
87
+ [--system-prompt <text-or-file>] [--append-system-prompt <text>]
88
+ [--no-context-files|-nc]
89
+ [--tools names] [--exclude-tools names] [--no-tools]
90
+ [--allow-tools names] [--approve|--no-approve]
91
+ [--theme <name|path>]
92
+ [--skill <path>] [--no-skills]
93
+ [--prompt-template <path>] [--no-prompt-templates]
94
+ [-p <prompt>] [prompt...]
95
+ ```
96
+
97
+ - No prompt, a tty: interactive session.
98
+ - A prompt, or `-p` / `--print`: one shot, then exit. `-p` puts only the
99
+ assistant's final text on stdout; the banner, usage, thinking, and tool
100
+ lines go to stderr. Piped stdin is appended after the command-line prompt.
101
+ - A workspace that already holds a session is resumed. `--fresh` opens a
102
+ second session beside it. Nothing is deleted.
103
+ - A leftover `.xeno/log` is not migrated; continue it with `--log .xeno/log`.
104
+
105
+ Slash commands that exist in the session (from `/help`):
106
+
107
+ ```
108
+ /model [id] search models, or switch (cuts a node)
109
+ /models refresh fetch models.dev into ~/.xeno/models.json
110
+ /login [provider] choose a login method, or store an API key
111
+ /login claude-pro|codex|github-copilot
112
+ subscription login (first-party client credentials)
113
+ /logout [provider] remove a stored credential
114
+ /new start a new session beside this one
115
+ /resume [id] search sessions, or switch to one
116
+ /name <name> name this session
117
+ /session id, path, events, usage
118
+ /back [n [files|context]] [confirm]
119
+ /tree search the session tree; navigate context at an exchange boundary
120
+ /fork [n] choose an exchange to copy into a new session
121
+ /clone copy the active branch into a new session
122
+ /trust [here|parent|never]
123
+ /settings [key v] theme, thinking, statusBar
124
+ /hotkeys bindings in effect
125
+ /usage /state /help /quit
126
+ ```
127
+
128
+ Type `/` for commands, templates and skills. The editor completes command
129
+ arguments and paths. `@file` or `@"file name"` attaches a project file anywhere
130
+ outside code literals; choosing a file keeps the prompt in the editor.
131
+ At line start, `!cmd` runs in the workspace and sends stdout/stderr/exit to the model; `!!cmd` runs without
132
+ sending output. During a turn, Enter queues steering (delivered after the
133
+ current tools finish); Alt+Enter queues a follow-up for after the turn.
134
+ Escape interrupts; pressing Escape twice on an empty idle editor opens the tree.
135
+ Ctrl+C clears the draft, then a quick second press exits; Ctrl+D exits an empty
136
+ editor. Ctrl+O and Ctrl+T toggle tools and thinking throughout the history.
137
+
138
+ `xeno --list-models` prints the built-in catalog (12 providers, 23 models)
139
+ and whether a credential resolved. `xeno models --refresh` writes
140
+ `~/.xeno/models.json`; startup never fetches.
141
+
142
+ ## Credentials
143
+
144
+ Order: `--api-key`, then `~/.xeno/auth.json`, then the provider's environment
145
+ variable. A stored entry owns that provider — the environment is not consulted
146
+ after a stored value fails.
147
+
148
+ Interactive sessions can start with no key and wait for `/login`. `-p` and
149
+ non-tty runs exit 1 and name `--api-key` or the variable; they do not mention
150
+ `/login`.
151
+
152
+ Keys in `auth.json` may be a literal, `!cmd`, or `$VAR`. The file is written
153
+ mode `0600`.
154
+
155
+ ## Isolation
156
+
157
+ This is not a sandbox. Tools run with the permissions of the user who
158
+ started xeno. `read` / `write` / `edit` stay inside the workspace root;
159
+ `bash` does not — it asks before it runs so you can see the command. That
160
+ ask is the last look at what is about to happen, **not a security
161
+ boundary**.
162
+
163
+ For untrusted repositories, generated code you do not intend to watch, or
164
+ unattended automation, run xeno inside a container (or a VM) that holds
165
+ only the files and credentials the task needs.
166
+
167
+ Project trust (`/trust`, `--approve`) only decides whether to load
168
+ `.xeno/settings.json` (and project-level skills, prompts, and UI files).
169
+ It does not change what tools can do. `AGENTS.md` is loaded whether the
170
+ project is trusted or not.
171
+
172
+ `--tools` / `--exclude-tools` / `--no-tools` remove tools from the schema
173
+ the model sees. `--allow-tools bash` lets bash run without asking, which
174
+ is what a CI job that has no tty has to pass explicitly. Without a tty,
175
+ an `ask` is a deny that names that flag.
176
+
177
+ ## Workspace data
178
+
179
+ A session writes into the workspace:
180
+
181
+ | Path | |
182
+ |---|---|
183
+ | `.xeno/sessions/<id>/` | node logs, handoffs, metadata journal, derived manifest, and observation artifacts |
184
+ | `.xeno/blobs/` | content-addressed file snapshots used by `/back` |
185
+
186
+ That tree grows with every turn and every snapshot. There is no garbage
187
+ collection yet. Dead branches from `/back` stay on disk.
188
+
189
+ `/fork` and `/clone` copy the log graph and observation artifacts, then select an
190
+ independent cursor. Historical nodes remain intact; a fork point limits the active
191
+ context. Workspace snapshot blobs remain shared under `.xeno/blobs/`.
192
+
193
+ Ignore it in the project:
194
+
195
+ ```
196
+ .xeno/
197
+ ```
198
+
199
+ Config and secrets live under `~/.xeno/` (or `$XENO_CONFIG_DIR`), not in
200
+ the workspace: `auth.json`, `settings.json`, `trust.json`, optional
201
+ `models.json` / `keybindings.json` / `prompts/` / `skills/`.
202
+
203
+ ## What this does
204
+
205
+ Shipped, not planned:
206
+
207
+ - An agent loop with tools `read`, `write`, `edit`, `bash`, `note`,
208
+ `branch`, `resolve`, `grep`, `find`, `ls`, `history`, `query`. `grep` / `find` use `rg` /
209
+ `fd` when those binaries are on `PATH`, otherwise a TypeScript walk;
210
+ both paths share one output format. They do not follow symlinks out of
211
+ the workspace.
212
+ - `history` searches the active ancestry, retrieves original events, and pages
213
+ immutable full observations. `query` filters and computes aggregates over
214
+ complete CSV, JSON, JSONL or text files, with source hashes. It evaluates a
215
+ finite query language, not generated code; resource-limit errors reject the
216
+ entire calculation instead of returning partial totals.
217
+ Missing JSON pointers identify the failed level and available keys without
218
+ copying source values. Writing branches inherit the same evidence tools,
219
+ subject to the parent's tool restrictions.
220
+ - Durable working-state guidance is appended when the next step is missing,
221
+ an action/observation cycle repeats, or the request budget is nearly exhausted.
222
+ Existing messages, the system prefix, and tool definitions stay frozen. Model
223
+ notes remain annotations and cannot manufacture verified completion.
224
+ - Two wire adapters (`anthropic-messages`, `openai-completions`). Both
225
+ stream; deltas are UI events and are not written to the log.
226
+ - A built-in catalog of **12** API-key providers and **23** models
227
+ (anthropic, openai, google, deepseek, xai, groq, cerebras, mistral,
228
+ openrouter, zai, moonshotai, together). `~/.xeno/models.json` overrides
229
+ by id. Startup does not hit the network.
230
+ - Sessions, resume, `/new` / `/resume` / `/name` / `/session`, `/back`
231
+ (files, context, or both), `/tree`, read-only `branch` / `resolve`, and
232
+ write branches in their own git worktree.
233
+ - Progress tracking counts new read observations and fresh successful checks.
234
+ Repeated content, renamed completion claims and gate regressions do not reset
235
+ the three-segment stop rule. This is an anti-stall heuristic, not proof of
236
+ relevance or completion; [`research M.4`](docs/agent-harness-research.md) states its limits.
237
+ - A terminal UI using the pinned Pi renderer, raw input, editor, autocomplete,
238
+ Markdown, searchable pickers and settings. Durable history is a pure log
239
+ projection across handoff boundaries. Includes tool diffs, historical folds,
240
+ Pi dark/light palettes, `keybindings.json`, `/settings` and `/hotkeys`.
241
+ - Context files: `AGENTS.md` / `CLAUDE.md` and the override chain, plus
242
+ `--system-prompt` / `--append-system-prompt` / `-nc`.
243
+ - Skills (`~/.xeno/skills/`, `/skill:name`) and prompt templates
244
+ (`~/.xeno/prompts/`, `/name`). Untrusted projects do not load the
245
+ project-level copies.
246
+ - `/login` for API keys and for Claude Pro/Max, Codex, and GitHub
247
+ Copilot subscription flows. See Known gaps.
248
+
249
+ ## Known gaps
250
+
251
+ - **No sandbox.** See Isolation. That is a decision, not a missing feature.
252
+ - **Token counts are an estimate** (`chars/3.6`, labelled `kind: "estimate"`).
253
+ The provider `count_tokens` API is not wired. Boundaries therefore fire
254
+ on an approximation.
255
+ - **OAuth has not been run against a live authorization or token endpoint.**
256
+ The three flows exist and pass tests against an injected transport.
257
+ - **Tiered provider prices are stored as the base rate.** Example: the
258
+ catalog comment on `gpt-5.5-pro` notes a `context_over_200k` tier that
259
+ is not applied. `/usage` reports `{kind:"partial"}` when cache prices
260
+ are missing; it does not treat a missing cache price as zero.
261
+ - **No packages or extensions.** Skills and templates are files on disk.
262
+ There is no installer.
263
+ - **Bun only.** Node has not been tested.
264
+ - **`/back` does not restore symlinks.**
265
+ - **`.xeno/` is not collected.** Snapshots and abandoned session branches
266
+ stay until you delete them.
267
+ - **No telemetry, `/share`, auto-update, or `/compact`.** Those are
268
+ refusals (`docs/PI_PARITY.md` D15, D16), not backlog.
269
+
270
+ `docs/TUI_ARCHITECTURE.md` describes the active interaction path and verification.
271
+ `docs/PI_PARITY.md` records deliberate differences and remaining Pi features.
272
+ Pi's editor supports undo, not redo; an old `editor.redo` binding is diagnosed
273
+ as unsupported rather than silently accepted. Ctrl+- and the existing Ctrl+Z
274
+ alias invoke Pi undo.
275
+
276
+ ## Why this shape
277
+
278
+ Three constraints, each of which rules out the obvious alternative.
279
+
280
+ **In-place eviction is the worst option under prefix caching.** Releasing tokens from
281
+ the middle of the context changes the prompt prefix, which invalidates the cached KV
282
+ state for everything after it. Under sustained pressure you pay the cache-write cost on
283
+ every request and never amortize it through reads — worse than compaction, which at
284
+ least produces one stable prefix per pass (this is the failure mode
285
+ [Beyond Compaction](https://arxiv.org/abs/2606.11213) documents). So token release must
286
+ be a rare, coarse event, not a continuous process. Hence segments.
287
+
288
+ **Summarization is unpredictably lossy, and the loss is undetectable from the result.**
289
+ What a summarizer keeps depends on its judgment of salience at that moment, which need
290
+ not match what the next segment needs — and you cannot tell from the compacted context
291
+ that anything is missing. Worse, the thing summarizers reliably drop is the record of
292
+ failed attempts, so the agent repeats them. Hence a deterministic projection plus a log
293
+ that is still on disk: a handoff that omits something is recoverable, because the agent
294
+ can go read it.
295
+
296
+ **A handoff that is only prose is autocompact with extra steps.** If the receiving agent
297
+ cannot interrogate what was compressed away, the structure bought nothing. The log
298
+ being intact is what makes the difference real rather than rhetorical.
299
+
300
+ ## What is actually enforced
301
+
302
+ The interesting part is not the field list. It is that the harness — the only layer that
303
+ sees prompt assembly, tool mediation and lifecycle events together — can enforce a
304
+ contract the prompt cannot.
305
+
306
+ **A completion claim must cite a log event that exited zero.** The model calls a claim
307
+ tool naming a gate; `deriveHandoff` resolves that citation against command exit codes
308
+ recorded in the log. Unresolvable claims land in `state.unverified_claims` with a
309
+ reason. They are neither accepted nor silently dropped, and the rendered prompt says
310
+ they must be treated as not done.
311
+
312
+ This directly targets the "migration applied successfully" failure: a tool call returned
313
+ a nonzero exit code, nothing in the loop read it, and the model wrote a confident summary
314
+ of a result it never saw. Here the model's account of a result carries no weight at all.
315
+
316
+ Two related rules:
317
+
318
+ - **Claims resolve against evidence at or before the claim.** A later failing run does
319
+ not erase the recorded successful execution — appending an event must never
320
+ rewrite that observation. That the gate is red *now* is separate
321
+ information, carried by `verification` and repeated inline as `evidence_superseded`
322
+ so a reader of `completed` cannot miss it.
323
+ - **Evidence must come from the same segment.** A gate that passed three segments ago
324
+ is not proof about code that has changed since.
325
+
326
+ Other invariants (`src/core/handoff.ts`, `validateHandoff`): a `pass` status requires a
327
+ zero exit; every file anchor carries a digest; every handoff identifies its source
328
+ node and digest; a continuation is valid only with both remaining work and a concrete next
329
+ action. A property test asserts that `deriveHandoff` can never produce a handoff that
330
+ violates any of them.
331
+
332
+ **Append references, never copies.** A boundary event stores the id of the handoff it
333
+ produced and a ref to its predecessor — never the history it replaces. Codex CLI
334
+ re-embeds the full prior history into its rollout JSONL on every compaction, which is
335
+ quadratic on disk and has been reported at 42 GB
336
+ ([openai/codex#41806](https://github.com/openai/codex/issues/41806)). The types here
337
+ cannot express recursive history copies. Full structured state can still grow as
338
+ new requirements, decisions and files accumulate. Handoff v2 carries a bounded
339
+ suffix of each section and records its immutable source and exact ranges. The
340
+ `history` tool rebuilds omitted pages from the log; no model summary is involved.
341
+
342
+ The carried-state budget is 5,000 estimated tokens. A 100-node deterministic
343
+ measurement grows the full state to about 14.7k while the carried view remains
344
+ at or below 5k. This bounds the prompt payload; it does not claim constant disk
345
+ space, replay time, or working memory for reconstructing the full history. The
346
+ objective stays verbatim: an objective that cannot fit is refused explicitly.
347
+
348
+ Session moves are committed to `metadata.jsonl`; `manifest.json` is a replaceable
349
+ index rebuilt from it. Sealed node digests are checked on resume and restore.
350
+ Interrupted tools receive an explicit unknown outcome instead of being run again.
351
+ Tool previews that exceed their display budget include a content-addressed
352
+ reference to the complete observation.
353
+
354
+ Command evidence includes the observed workspace digest when scanning succeeds.
355
+ Later edits mark old verification and completions as stale. A zero exit proves
356
+ the recorded command succeeded; it does not prove the model's description is an
357
+ adequate specification or that the test suite covers every requirement.
358
+
359
+ ## Segment length
360
+
361
+ New sessions default to **windowed replay**: preserve the active dialogue until model
362
+ window pressure calls for a new projection. With an explicit model window, reserve
363
+ the configured output tokens, prepare at 70% of the remaining capacity and cut at
364
+ 80%. These are conservative hypotheses, not measured optima. Unknown model capacity
365
+ uses the fixed segmented policy below; it is never treated as unlimited.
366
+
367
+ The explicit segmented policy retains **30k trigger / 40k hard**. Its historical cost model
368
+
369
+ ```
370
+ per_turn(L) = 0.05*(H+L) + 1.25*d + 1.25*(H+R)*d/(L-H)
371
+ ```
372
+
373
+ (cache reads at 0.1x, writes at 1.25x; `H` = handoff size, `d` = per-turn growth,
374
+ `R` = re-read cost after a boundary) was first evaluated with `H` *assumed* to be ~10k,
375
+ which put the optimum near 37k. The original single-segment measurement
376
+ (`prev=null`) gave the following historical figures:
377
+
378
+ | turns | segment L | handoff H | H/L |
379
+ |------:|----------:|----------:|----:|
380
+ | 10 | 4.8k | 366 | 7.6% |
381
+ | 50 | 25k | 986 | 3.9% |
382
+ | 200 | 99k | 2218 | 2.2% |
383
+ | 400 | 203k | 3201 | 1.6% |
384
+
385
+ Those samples do not establish a bound over a continuous session: deduplication
386
+ does not remove new, distinct facts. `bun run measure` now reports both this local
387
+ diagnostic and 100 consecutive derivations with retained state. The cost formula
388
+ models input caching at the stated rates; it omits task quality and cannot prove
389
+ 30k/40k is globally optimal. These legacy bounds retain their 5k work floor.
390
+
391
+ `scripts/ab-suite.ts` now separates node, replay, round-preserving truncation and
392
+ observation masking. It uses a seeded shuffled schedule and includes output cost
393
+ and failed outcomes. Historical A/B figures predate these corrections and cannot
394
+ establish superiority over the corrected baselines. No new live-model result is
395
+ claimed until that experiment is run.
396
+
397
+ There is also a floor with a principled reason: **a handoff only pays for itself once a
398
+ segment's history exceeds the handoff.** Below that, replaying the history is cheaper —
399
+ `test/log.test.ts` pins both sides of that crossover.
400
+
401
+ ## Layout
402
+
403
+ `SessionConfig.contextPolicy` and `--context-policy` select `windowed` (new-session
404
+ default), `segmented`, `continuous`, or the experimental `adaptive` policy. They
405
+ share the same durable log. Saved sessions keep their original policy, including
406
+ legacy logs without a policy, which resume as segmented. Explicit CLI `--trigger`
407
+ selects segmented unless `--context-policy` is also set. Continuous
408
+ keeps the dialogue until the provider window guard stops it; adaptive may defer a
409
+ soft cut when continuation state is missing or a conservative next-input cost
410
+ estimate favors keeping the cached prefix. It still obeys the hard boundary.
411
+ Windowed cuts keep a bounded recent suffix of complete exchanges alongside the
412
+ derived handoff, through event references rather than copied transcript bytes.
413
+ The replacement targets half the usable window and at most 20k recent-history
414
+ tokens; these are testable assumptions. New prompts are checked before dispatch.
415
+ If no useful replacement fits, the session stops explicitly. Retained events do
416
+ not count twice toward derived state, and model switches discard old thinking.
417
+ The segmented 30k/40k/5k values are unchanged. Missing working state gets one
418
+ append-only preparation notice before the boundary. These mechanisms do not
419
+ guarantee model compliance or lower whole-task costs; see research N.6.
420
+
421
+ New sessions also stop before another model call after 12 complete tool exchanges
422
+ without new observed evidence or fresh verification, with one warning halfway.
423
+ This watchdog is independent of context cuts and reconstructs from original log
424
+ events. `SessionConfig.stallLimit` accepts 0 to disable or an integer of at least 2;
425
+ old saved sessions without it retain their prior behavior. Novel content need not
426
+ be relevant, so this is a resource guard, not a task-success judge (research N.9).
427
+
428
+ Dependency direction is one-way, enforced mechanically by
429
+ `scripts/check-boundaries.ts`:
430
+
431
+ ```
432
+ apps -> runtime -> store -> core
433
+ ```
434
+
435
+ Every path below was checked with `test -f` on this checkout.
436
+
437
+ | Layer | |
438
+ |---|---|
439
+ | **`src/core/`** | **pure**: no I/O, no clock, no randomness, no model call |
440
+ | `core/events.ts` | log event types; harness-derived vs model-annotated split |
441
+ | `core/handoff.ts` | handoff schema, YAML field order, `validateHandoff` invariants |
442
+ | `core/derive.ts` | `deriveHandoff` — events + previous handoff -> next handoff |
443
+ | `core/project.ts` | `projectContext` — log -> messages |
444
+ | `core/transcript.ts` / `file-references.ts` | pure human-history projection and explicit file references |
445
+ | `core/boundary.ts` | `shouldCut`, `shouldStopAndAsk` |
446
+ | `core/continuation.ts` / `history-query.ts` | append-only guidance and ancestry search contracts |
447
+ | `core/context-policy.ts` | windowed replay, legacy policies and conservative cache-cost estimates |
448
+ | `core/context-window.ts` | model-window budgets and exact recent-exchange retention |
449
+ | `core/data-query.ts` | deterministic parsing, filtering, grouping and aggregates |
450
+ | `core/yaml.ts` | restricted deterministic YAML emitter, no dependencies |
451
+ | `core/tokens.ts` | token counter interface; the bundled one is an estimate and says so |
452
+ | `core/permissions.ts` | `decide` / `selectTools` — pure rules; approval is a last look, not a boundary |
453
+ | `core/skills.ts` / `prompts.ts` / `frontmatter.ts` | skill text and `/name` templates |
454
+ | `core/settings.ts` / `theme.ts` / `keymap.ts` | settings merge, themes, bindings |
455
+ | `core/queue.ts` | steering / follow-up queue |
456
+ | **`src/store/`** | the only I/O: node JSONL, handoff files, manifest, blobs |
457
+ | `store/log.ts` / `sessions.ts` | node tree and `.xeno/sessions/<id>/` |
458
+ | `store/config.ts` | `~/.xeno/` paths; `auth.json` is 0600 |
459
+ | `store/search.ts` | grep / find / ls walk |
460
+ | `store/context-files.ts` | AGENTS.md discovery |
461
+ | **`src/runtime/`** | impure but thin; the only layer allowed npm packages |
462
+ | `runtime/provider.ts` / `openai.ts` | the two wire formats |
463
+ | `runtime/catalog.ts` / `model.ts` | 12 providers, 23 models |
464
+ | `runtime/tools.ts` / `history-tools.ts` | confined tools, complete query results and history retrieval |
465
+ | `runtime/benchmark.ts` | actual Session trials, bounded spending and cache diagnostics |
466
+ | `runtime/loop.ts` | orchestration; every judgement delegates to a pure function in core |
467
+ | `runtime/auth.ts` / `oauth.ts` | credential order and subscription flows |
468
+ | **`src/vendor/pi-tui/`** | unmodified Pi terminal and component library, pinned in `VENDOR.md` |
469
+ | **`src/tui/`** | Pi input, component and theme adapters; no harness imports |
470
+ | `tui/markdown.ts` / `toolview.ts` / `statusline.ts` / `tree.ts` | display |
471
+ | **`src/apps/`** | `cli.ts` — Pi main-screen UI for TTY, clean print/JSON output otherwise |
472
+ | `apps/startup.ts` / `shell.ts` / `commands.ts` | flags, session, slash commands |
473
+
474
+ ## Properties the tests pin
475
+
476
+ - **Identity.** With no handoff, the projection is the identity mapping of harness
477
+ events onto messages. Segment 0 behaves exactly like a harness with no context
478
+ management, so nothing is paid for until a boundary actually fires.
479
+ - **Prefix stability.** Appending an event only appends to the projection; it never
480
+ rewrites an earlier message. This pins prefix stability; actual cache hits
481
+ remain provider-dependent.
482
+ - **Purity.** Identical inputs produce byte-identical YAML. `deriveHandoff` mutates
483
+ neither its inputs nor the previous handoff.
484
+ - **Nothing silently dropped.** Every claim appears in exactly one of `completed` or
485
+ `unverified_claims`; state buckets are pairwise disjoint; accumulated state is
486
+ monotonic across segments.
487
+ - **No history copies.** Tool output is referenced rather than recursively embedded.
488
+ The carried handoff is budgeted; full structured state remains reconstructible.
489
+ - **YAML round-trips.** Emit -> parse -> re-emit is byte-identical, verified against a
490
+ real parser, with an idempotency gate and golden snapshots.
491
+
492
+ ```
493
+ bun run gate # bun test + tsc --noEmit + the boundary checks
494
+ bun run measure # the segment-length measurement
495
+ bun run regen-golden # regenerate snapshots, then read the diff
496
+ ```
497
+
498
+ Design notes: `docs/agent-harness-research.md`.
@@ -0,0 +1,130 @@
1
+ # Install xeno
2
+
3
+ Published as `@visual-z/xeno`. The license is `UNLICENSED`: the package is
4
+ public to read and install, and no right to reuse the code is granted.
5
+
6
+ ```bash
7
+ npm i -g @visual-z/xeno # or: bun add -g @visual-z/xeno
8
+ ```
9
+
10
+ The installed `xeno` still runs with Bun (below). Installing from a
11
+ checkout works the same way and is described after it.
12
+
13
+ ## Bun, not Node
14
+
15
+ The entry point is `src/apps/cli.ts` with `#!/usr/bin/env bun`. The
16
+ process uses Bun APIs (`Bun.file`, `Bun.spawn`, `Bun.spawnSync`,
17
+ `for await (const line of console)`). Those are why the runtime is Bun.
18
+
19
+ `engines.bun` is `>=1.1.0`. This checkout was run on **Bun 1.3.10**.
20
+ Node is untested. That is a fact about this tree, not a promise that
21
+ Node support is coming.
22
+
23
+ Install Bun from https://bun.sh.
24
+
25
+ ## From a checkout
26
+
27
+ ```bash
28
+ git clone <this-repo> xeno
29
+ cd xeno
30
+ bun install
31
+ ```
32
+
33
+ `bun install` pulls the one runtime dependency (`@anthropic-ai/sdk`)
34
+ and the TypeScript dev tooling.
35
+
36
+ Run the CLI without linking:
37
+
38
+ ```bash
39
+ bun src/apps/cli.ts --help
40
+ ```
41
+
42
+ Or put `xeno` on your PATH from this checkout:
43
+
44
+ ```bash
45
+ bun link
46
+ ```
47
+
48
+ Then, in any directory:
49
+
50
+ ```bash
51
+ xeno --help
52
+ xeno --list-models
53
+ ```
54
+
55
+ `bun link` writes a shim that executes the `bin` field
56
+ (`src/apps/cli.ts`) with Bun. It does not copy the tree.
57
+
58
+ A workspace that already has a session is resumed. `--fresh` starts a
59
+ new session beside it; nothing is deleted.
60
+
61
+ ## Credentials
62
+
63
+ Resolution order, first match wins:
64
+
65
+ 1. `--api-key`
66
+ 2. `~/.xeno/auth.json` (created `0600` by `/login`)
67
+ 3. the provider's environment variable
68
+
69
+ A stored entry owns that provider. The environment is not a fallback
70
+ after a stored key fails.
71
+
72
+ | Provider | Environment variable |
73
+ |-------------|-----------------------------------------------------------|
74
+ | anthropic | `ANTHROPIC_API_KEY` |
75
+ | openai | `OPENAI_API_KEY` |
76
+ | google | `GOOGLE_API_KEY` or `GOOGLE_GENERATIVE_AI_API_KEY` or `GEMINI_API_KEY` |
77
+ | deepseek | `DEEPSEEK_API_KEY` |
78
+ | xai | `XAI_API_KEY` |
79
+ | groq | `GROQ_API_KEY` |
80
+ | cerebras | `CEREBRAS_API_KEY` |
81
+ | mistral | `MISTRAL_API_KEY` |
82
+ | openrouter | `OPENROUTER_API_KEY` |
83
+ | zai | `ZHIPU_API_KEY` |
84
+ | moonshotai | `MOONSHOT_API_KEY` |
85
+ | together | `TOGETHER_API_KEY` |
86
+
87
+ Interactive: export one of those, or run `/login <provider>`.
88
+ `-p` / no tty: pass `--api-key` or export the variable. `/login` is
89
+ not a script path.
90
+
91
+ `/login claude-pro`, `/login codex`, and `/login github-copilot` start
92
+ subscription flows (authorization-code + PKCE, or a device code for
93
+ Copilot). They use first-party client credentials; the terms-of-service
94
+ risk is on your account. Those flows have been tested against injected
95
+ transports, not against a live authorization endpoint.
96
+
97
+ ## What a run writes
98
+
99
+ In the **workspace**:
100
+
101
+ ```
102
+ .xeno/sessions/<id>/ node-NNNN.jsonl, handoff-NNNN.yaml, manifest
103
+ .xeno/blobs/ content-addressed snapshots for /back
104
+ ```
105
+
106
+ That directory grows. There is no garbage collection.
107
+
108
+ In **your home directory** (`~/.xeno/`, or `$XENO_CONFIG_DIR`):
109
+
110
+ ```
111
+ auth.json credentials (0600)
112
+ settings.json theme, thinking, defaults
113
+ trust.json which directories may load project settings
114
+ models.json optional catalog overlay (written by `xeno models --refresh`)
115
+ keybindings.json optional
116
+ prompts/ skills/ optional
117
+ ```
118
+
119
+ A leftover `.xeno/log` from before named sessions is not moved. Continue
120
+ it with `--log .xeno/log`.
121
+
122
+ Add this to the project's `.gitignore`:
123
+
124
+ ```
125
+ .xeno/
126
+ ```
127
+
128
+ ## Untrusted work
129
+
130
+ There is no sandbox. See the Isolation section in `README.md`.
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@visual-z/xeno",
3
+ "version": "0.1.0",
4
+ "license": "UNLICENSED",
5
+ "type": "module",
6
+ "description": "Segmented append-only agent log with pure context and handoff projections",
7
+ "bin": {
8
+ "xeno": "src/apps/cli.ts"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "README.md",
13
+ "docs/install.md"
14
+ ],
15
+ "engines": {
16
+ "bun": ">=1.1.0"
17
+ },
18
+ "repository": {
19
+ "type": "git"
20
+ },
21
+ "scripts": {
22
+ "test": "bun test",
23
+ "typecheck": "bun run scripts/typecheck.ts",
24
+ "check": "bun run scripts/check-boundaries.ts",
25
+ "gate": "bun test && bun run typecheck && bun run scripts/check-boundaries.ts",
26
+ "verify-red": "bun run scripts/verify-red.ts",
27
+ "regen-golden": "bun run scripts/regen-golden.ts",
28
+ "measure": "bun run scripts/measure-handoff.ts",
29
+ "agent": "bun run src/apps/cli.ts"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "^5.7.0",
33
+ "@types/bun": "latest"
34
+ },
35
+ "dependencies": {
36
+ "@anthropic-ai/sdk": "^0.127.0",
37
+ "get-east-asian-width": "1.6.0",
38
+ "marked": "18.0.11"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }