Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# TokenEconomizer

A plugin for Claude Code, Codex, the Copilot CLI and Pi (experimentally opencode, Cursor, Grok Build,
Hermes Agent and the Antigravity CLI) that reads finished sessions and changes what later sessions
in the same repository pay for: micro-skills, tool pruning, and a loop guard.
Hermes Agent, the Antigravity CLI and Factory Droid) that reads finished sessions and changes what
later sessions in the same repository pay for: micro-skills, tool pruning, and a loop guard.
See `README.md` for the overview and the method, `docs/PLUGIN.md` for the design and the verified
agent behavior, and `docs/ARCHITECTURE.md` for how the package fits together.

Expand All @@ -12,15 +12,16 @@ agent behavior, and `docs/ARCHITECTURE.md` for how the package fits together.
- `toolprune/` — unused-tool evidence and the deny-list proposal; Codex capability packages
- `loopguard/` — the per-call failure-loop guard and its per-repository envelope
- `trace/` — each agent's session-log format (Claude Code, Codex, Copilot CLI, Pi, opencode,
Cursor, Grok Build, Hermes Agent, Antigravity CLI)
Cursor, Grok Build, Hermes Agent, Antigravity CLI, Factory Droid)
- `plugin/` — the runtime: hook dispatch, per-repository state, settings, the `te` CLI
- `config.py`, `ledger.py`, `pricing.py`, `domain.py`, `stage.py` — shared by all three
- `plugin/` — the installable plugin directory: manifests per agent, hook files, launchers
(`scripts/te`, `scripts/te-hook`), slash commands (`slash-commands/`, named in the Claude Code
and Copilot manifests, since Codex would turn a `commands/` folder into duplicate skills), Codex
skills (`codex-skills/`, Codex having no plugin slash commands), the Pi extension, the opencode
plugin (`opencode/`, one file whose default export serves 1.x via `server` and 2.x via
`setup`) and the Hermes plugin; `lib/` links the package
`setup`), the Hermes plugin, and the Droid manifest (`plugin/.factory-plugin/`, with
`plugin/hooks/droid-hooks.json`); `lib/` links the package
- `experiments/` — benchmark-only: `te_bench/` (adapters, arms, facade, benchmark-only
controllers), `te_harness/` (Docker runners), `scripts/`, `workloads/`, its own `tests/`;
its `README.md` is the run book
Expand Down
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ mechanisms:
itself.

TokenEconomizer is a plugin for **Claude Code**, **Codex**, the **GitHub Copilot CLI** and **Pi**,
with experimental support for **opencode**, **Cursor Agent**, **Grok Build**, **Hermes Agent** and
the **Antigravity CLI**. Each italic
with experimental support for **opencode**, **Cursor Agent**, **Grok Build**, **Hermes Agent**,
the **Antigravity CLI** and **Factory Droid**. Each italic
letter above is a setting, shown with its default ([Settings](#settings)), and each mechanism can be turned on or
off. The plugin announces each action in one line, and if any part of it fails, your session
carries on as if it were not installed. Its model calls (the fact proposer, and the judge with the
Expand Down Expand Up @@ -167,6 +167,24 @@ pi install git:github.com/microsoft/TokenEconomizer

Start `pi` and run `/te status`.

**Factory Droid** (experimental; micro-skills, the loop guard and session cost, not tool pruning)

```sh
git clone https://github.com/microsoft/TokenEconomizer.git
cd TokenEconomizer
python scripts/build_plugin.py --droid
droid plugin marketplace add "$PWD/dist/tokeneconomizer-droid"
droid plugin install tokeneconomizer@tokeneconomizer-droid --scope user
```

Start a new Droid session (its hooks are snapshotted at startup) and run
`/tokeneconomizer-status`. Droid keeps each session's token usage in a settings file beside its
log, so folded sessions are priced like any other host's. Its TUI has no persistent per-tool
setting, so tool pruning stays off; and because `droid exec` writes a session log of its own,
micro-skills' paid proposer and the loop guard's judge stay off until `droid_sidecall` is on
(when on, they may use only Droid's read-only `Read` tool).
See **[`docs/DROID.md`](docs/DROID.md)** for what works and how to import earlier sessions.

**Cursor Agent** (experimental)

```sh
Expand Down Expand Up @@ -249,6 +267,7 @@ To update:
| Agent | Update |
|---|---|
| Claude Code, Copilot CLI, opencode | `git pull` in the clone |
| Factory Droid | `git pull` in the clone, then the `build_plugin.py --droid` and install lines again |
| Hermes Agent | `git pull` in the clone, then `hermes gateway restart` |
| Antigravity CLI | build the bundle again and `agy plugin install` it |
| Codex | `git pull` in the clone, then the `codex plugin add` line again |
Expand All @@ -270,6 +289,7 @@ uninstalling, using Copilot with your own model key, and troubleshooting.
| opencode (experimental) | 1.19.5 and 2.2.0 | not tested |
| Hermes Agent (experimental) | 0.22.5 | not tested |
| Antigravity CLI (experimental) | agy 1.5.1, macOS and Linux | not tested |
| Factory Droid (experimental) | 0.233.0, macOS (below) | not tested |

The commands above were tested on the oldest and newest version in each range; newer versions
have not been tested yet.
Expand Down Expand Up @@ -298,6 +318,13 @@ have not been tested yet.
not. Its other surfaces (the Ink TUI, the Desktop app, messaging platforms, cron) and
subagents are ignored, and tool pruning is not available. See
[its limits](plugin/README.md#hermes-agent).
- **Factory Droid** support is experimental: it was tested on 0.233.0 only, on macOS. Droid
documents the hook contract this adapter uses, and the plugin's own hook output is covered by
tests; a learned fact reaching a live Droid model was verified on 2026-11-05 (a new session
received the 793-character block as `hookSpecificOutput.additionalContext` and the model
repeated it). Droid's TUI has no persistent per-tool setting (Factory issue #65), so tool
pruning is not available; its session logs are read for micro-skills and the loop guard, and
priced from the usage Droid writes beside each log. See [its notes](docs/DROID.md).

### Everyday commands

Expand All @@ -314,13 +341,14 @@ commands. They have the same names on every agent; only the way you type them di
| Grok Build | from a terminal: `~/.tokeneconomizer/bin/te --host grok <command>` |
| Hermes Agent | from a terminal: `~/.tokeneconomizer/bin/te --host hermes <command>` |
| Antigravity CLI | from a terminal: `~/.tokeneconomizer/bin/te --host antigravity <command>` |
| Factory Droid | `/tokeneconomizer-<command>` (e.g. `/tokeneconomizer-status`) |
| a terminal, in the repository | `~/.tokeneconomizer/bin/te <command>` |

| Command | What it does |
|---|---|
| `status` | what it learned and did here, and what your sessions cost |
| `microskills` | the facts it injects, and those still forming |
| `toolprune` | the tools it left out, and the evidence (not on Pi, opencode, Hermes Agent or the Antigravity CLI) |
| `toolprune` | the tools it left out, and the evidence (not on Pi, opencode, Hermes Agent, the Antigravity CLI or Factory Droid) |
| `toolprune restore` | give them back; they will not be removed again |
| `config` | every setting in effect |
| `config set microskills=on` | turn a mechanism on (or `=off`) in this repository; add `--global` for every repository |
Expand Down Expand Up @@ -351,6 +379,7 @@ are these settings:
| *c* | `loopguard.judge_confidence` | 0.7 | how sure the judge must be that a turn is stuck |
| *r* | `loopguard_nudges` | 1 | redirects per turn before the guard only tells you |
| *b* | `loopguard.backstop_mult` | 2.5 | how many times its usual length a churning turn may run before it is redirected without the judge |
| | `droid_sidecall` | off | on Droid, allow the plugin's own model calls, which run `droid exec` |

Change them with `config set`, shown here in Claude Code's form (other agents type it as in
[Everyday commands](#everyday-commands)):
Expand All @@ -362,6 +391,12 @@ Change them with `config set`, shown here in Claude Code's form (other agents ty
/tokeneconomizer:config set loopguard_nudges=2 # r = 2: redirect twice before telling you
```

On Droid, the plugin's own calls run `droid exec`, which writes a session log of its own, so
they stay off until `config set droid_sidecall=on` (`TE_DROID_SIDECALL=1` does the same from a
terminal). When on, they run with `--only-tools Read`, so `Read` is the only tool they may use.
With them off, `microskills=envfacts` still learns and the loop guard keeps its rule-based
backstop; only the paid fact proposer and the judge wait.

Tool pruning counts prompts so that long sessions weigh more, and requires *m* sessions so that a
single session never decides alone. It writes the deny list only once your sessions in the
repository have been idle for *q* minutes (or, at the latest, a day after the list formed): a
Expand Down Expand Up @@ -404,6 +439,7 @@ what it can leave out differs:
| opencode | nothing yet | — |
| Hermes Agent | nothing yet | — |
| Antigravity CLI | nothing yet | — |
| Factory Droid | nothing (no persistent per-tool setting in the TUI) | — |

Tool pruning adds only its own entries and never touches deny rules you wrote; a settings file it
creates is kept out of git (`.git/info/exclude`). On Claude Code, the session-start line that says
Expand Down Expand Up @@ -439,6 +475,7 @@ Where that line appears, and how to stop the turn, in the terminal UI and in a h
| Pi | as a notification | Esc | on stderr | Ctrl+C |
| opencode | as a notification | Esc | on stderr | Ctrl+C |
| Hermes Agent | in the model's reply | Ctrl+C | in the final reply | Ctrl+C |
| Factory Droid | in the model's reply | Interrupt the agent | in the final reply | Ctrl+C |

- **Claude Code** shows a plugin's own lines only in its detailed transcript (Ctrl+O), so the
plugin asks Claude to repeat this one in its reply; the model may word it differently or leave
Expand All @@ -462,9 +499,9 @@ agent's own limits; afterwards, `te status` counts the redirects and "still stuc
| [`tokeneconomizer/loopguard`](tokeneconomizer/loopguard/) | Failure-loop intervention: the per-call guard and its per-repository envelope |
| [`tokeneconomizer/trace`](tokeneconomizer/trace/) | Each agent's session log, read into one shape |
| [`tokeneconomizer/plugin`](tokeneconomizer/plugin/) | The runtime the hooks and the `te` command run |
| [`plugin`](plugin/) | The installable plugin: manifests, hook files, launchers, slash commands, the Pi extension, the opencode and Hermes plugins |
| [`plugin`](plugin/) | The installable plugin: manifests, hook files, launchers, slash commands, the Pi extension, the opencode and Hermes plugins, the Droid bundle (`.factory-plugin/`) |
| [`experiments`](experiments/) | The harness, launchers and task sets behind the paper's results |
| [`docs`](docs/) | `PLUGIN.md` (design, verified agent behavior) and `ARCHITECTURE.md` (how the package fits together) |
| [`docs`](docs/) | `PLUGIN.md` (design, verified agent behavior), `ARCHITECTURE.md` (how the package fits together) and `DROID.md` (the Factory Droid adapter) |
| [`tests`](tests/) | Offline tests for the package and the plugin |

The package is standard-library only. Run the tests with `python -m pytest tests` (add
Expand Down
147 changes: 147 additions & 0 deletions docs/DROID.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# TokenEconomizer on Factory Droid

Droid support is experimental. It was built and tested against Droid `0.233.0` on macOS; no
other version has been tested.

## What works, and what does not

| Mechanism | Droid | Why |
|---|---|---|
| Micro-skills (repository facts at session start) | yes | `SessionStart` supports `hookSpecificOutput.additionalContext` |
| History import (learn from earlier sessions) | yes | `~/.factory/sessions/<workspace>/<id>.jsonl` has the tool calls |
| Session cost / effective tokens | yes | `<id>.settings.json` holds `tokenUsage` beside the log |
| Loop guard (redirect a failing loop) | partially | `PostToolUse` supports `additionalContext`; the judge needs `droid exec`, which is off by default |
| Tool pruning | no | Droid's TUI has no persistent per-tool setting; `droid exec --only-tools` has no interactive equivalent (Factory issue #65) |

The loop guard still learns its envelope from live turns that end normally, still redirects on
its rule signals, and still sends the "still stuck" notice through the model's reply, because
Droid documents no `systemMessage`. The model judge is what needs a side call, and is therefore
off by default.

## Why a session may learn nothing

Micro-skills reports what it has learned per repository. A directory that is not a git
repository is keyed by its own path, so starting every session in a folder that holds many
unrelated projects mixes evidence from all of them, and a fact from one is contradicted by
another. Start Droid in the project's own folder when you want it to learn that project.

The free layer (`microskills=envfacts`) learns only from repeated environment failures: a command
that fails the same way several sessions in a row and a working alternative. It needs 3
confirmations and 4 sessions in which the fact was exercised before it injects anything. A
repository whose sessions never run tests, builds or failing commands will produce no fact, by
design. The paid layer (`microskills=envfacts+distiller`, or `on`) asks the model for repository
knowledge every few sessions, and needs a side call.

## Install

```sh
git clone https://github.com/microsoft/TokenEconomizer.git
cd TokenEconomizer
python scripts/build_plugin.py --droid
droid plugin marketplace add "$PWD/dist/tokeneconomizer-droid"
droid plugin install tokeneconomizer@tokeneconomizer-droid --scope user
```

The bundle is a native Droid plugin: `.factory-plugin/plugin.json` at its root, hooks in
`hooks/hooks.json` in Droid's top-level shape, commands in `commands/`, and the Python package
in `lib/` (Droid copies the plugin into its cache, so a symlink would break). Start a new Droid
session after installing: Droid snapshots hooks at startup.

Check it in the repository:

```sh
~/.tokeneconomizer/bin/te --host droid --cwd "$PWD" status
```

## Import the sessions you already ran

A plugin installed today knows nothing about yesterday's sessions. Droid's own log directory is
read by the same history import the other hosts use:

```sh
~/.tokeneconomizer/bin/te --host droid --cwd "$PWD" history import
```

The import runs only into a repository with nothing folded yet, folds oldest first, skips
subagent sessions (`callingSessionId`), sessions with no prompt and sessions still being written,
and makes no model call. `history_import=on` lets the first session start in a repository do it
by itself.

Their usage is recorded, but `te status` counts imported sessions apart from the plugin's own
(`+N from earlier history`), because they ran before it: the totals in `sessions ... effective
tokens` cover the sessions that end after the plugin is installed.

## Settings

The plugin's settings live in `~/.tokeneconomizer/config.json`:

```sh
te --host droid config set microskills=envfacts # learn facts, no model call
te --host droid config set microskills=on # and let a model propose more, every k sessions
te --host droid config set droid_sidecall=on # allow the plugin's own model calls
te --host droid config set loopguard=nudge # the judge stays off until side calls are on
te --host droid config set toolprune=off # the only honest value on Droid
```

`droid_sidecall` is the switch for the calls described below; `TE_DROID_SIDECALL=1` in the
environment of the session does the same, for a Droid started from a terminal.

`TE_DISABLE=1` in a session's environment turns every hook into a no-op. `te --host droid doctor`
reports the Droid version, the session directory, side-call state and recent errors.

## Side calls are off by default

Micro-skills' paid proposer and the loop guard's judge normally call the agent's own CLI. On
Droid that is `droid exec`, which writes a session log of its own under `~/.factory/sessions`,
and Droid has no setting that leaves such a call with no tool at all. So the adapter does not make
that call unless it is allowed, by `config set droid_sidecall=on` or by `TE_DROID_SIDECALL=1` in
the session's environment. The setting is the one that also works from
the Factory desktop app, where a session's environment cannot be chosen. With the calls off,
`microskills=envfacts` still learns, and the loop guard keeps its rule-based backstop.

When it is on, the call runs in a throwaway directory at Droid's default read-only autonomy, with
its tools limited by a Droid setting rather than only by the prompt: `--only-tools Read` leaves
`Read` as the one tool it may use. That is as close to no tools as Droid allows (`--only-tools ""`
means no filter), and it matters: the default read-only autonomy alone still allows `Execute`,
the web tools and every MCP tool. Its cost is read from the usage Droid records for the call.

## What is verified, and what is not

- Verified locally: the session reader, prompt counting, the tool-name mapping, folding a
session, the history import, usage and pricing, the hook's own JSON output, and the loop
guard's redirect and notice paths. Each has a test in `tests/test_droid.py`.
- Documented by Factory and relied on here: `SessionStart` appends
`hookSpecificOutput.additionalContext`; `UserPromptSubmit` and `PostToolUse` accept
`additionalContext`; there is no `systemMessage` and no `PostToolUseFailure`.
- **Verified live on 2026-11-05**, on Droid 0.233.0: a new session (`bb18c4fc`) received the
793-character block as `hookSpecificOutput.additionalContext`, and the model repeated it
verbatim. The hook's JSON, Droid appending it to the model's context, and the model repeating
it have now all been observed end to end. The block was three facts at the time; a fourth was
added by the next fold.
- Verified on Droid 0.233.0: with the side call's own flags, `droid exec --list-tools` reports
`Read` as the only tool allowed, MCP tools included, and a live side call on 2026-11-07,
asked to run shell commands, write a file and search the web, called no tool and wrote
nothing. `tests/test_droid.py` pins the flag.

Droid's session and hook formats are internal. The adapter fails open: any error answers `{}` and
writes one line to `~/.tokeneconomizer/errors.log`, so a Droid update can at worst stop the plugin
from learning, never break a session.

## Uninstall and rollback

```sh
droid plugin uninstall tokeneconomizer@tokeneconomizer-droid --scope user
droid plugin marketplace remove tokeneconomizer-droid
rm -rf ~/.tokeneconomizer
```

The plugin writes nothing to `~/.factory/settings.json`, `~/.factory/hooks.json` or any
repository file: its hooks come from the plugin directory, its state from `~/.tokeneconomizer`.
Deleting that directory forgets everything it learned.

## Compatibility

Tested on Droid `0.233.0` (macOS), including the live injection of a learned block on
2026-11-05. After a Droid update, run one session and check `te --host droid doctor` and
`~/.tokeneconomizer/errors.log`. If Droid changes the session log shape,
`te --host droid history import` will import nothing, which is the safe failure.
Loading
Loading