# Upstream porting specification (maintainer reference) This document is the implementation-grade porting record produced from the upstream TokenEconomizer source at commit `e428fc91b0f8ffecb5a596d56b14fe2f1485a5f9` (Apache-2.0). It is kept in the repository so maintainers can check every threshold and behavior of the port against its source. Citations of the form `path:line` refer to the upstream checkout at that commit; internal research artifacts and machine-specific paths have been replaced with placeholders and are not part of this repository. --- # TokenEconomizer → DSH plugin: implementation-grade porting specification Status: DRAFT (sections filled incrementally by researcher-upstream; task-2) Upstream source: `upstream-te/` (full repo tarball, read-only; DO NOT modify). All `file:line` citations below are relative to `upstream-te/` unless stated otherwise. --- ## 1. PROVENANCE & LICENSE ### 1.1 Exact upstream identity | Fact | Value | Citation | |---|---|---| | Repository | `https://github.com/microsoft/TokenEconomizer` | `README.md:123`, `package.json:13` | | Commit | `e428fc91b0f8ffecb5a596d56b14fe2f1485a5f9` (2026-10-30, "Merge pull request #89 from microsoft/fix-claude-sidecall-mcp") | task brief; extracted tarball | | Package name/version | `tokeneconomizer` 0.1.0 | `pyproject.toml:6-7`; `package.json:2-3` | | Python | >= 3.10, **stdlib only** (zero runtime deps) | `pyproject.toml:10,15-18` | | License | Apache License 2.0 (SPDX `Apache-2.0`) | `LICENSE:1-202`; `pyproject.toml:11`; `package.json:12`; `README.md:386-388` | | Paper | "TokenEconomizer: Experience-Driven Black-Box Cost Control for Coding Agents", arXiv 2610.22732 (Li, Xu, Singh, Chen, Inala, Siska, Tamersoy, Yu, Stoica, Galley, Gao; UC Berkeley + Microsoft Research + Microsoft Security) | `README.md:1-27,370-384` | | Project page | `https://tokeneconomizer.github.io` | `README.md:23` | | NOTICE file | **None** in the tree (verified: only `LICENSE` at root; no `NOTICE*`) | `find`/grep over repo | | Per-file copyright headers | **None**; `grep -r Copyright` matches only `LICENSE` text | source-wide grep | | Bundled third-party data | `tokeneconomizer/trace/claude_tool_catalog.json` (a Claude Code tool-description snapshot; `pyproject.toml:58-60` package-data) | `trace/catalog.py` headers | ### 1.2 What Apache-2.0 requires of this port (quoted) `LICENSE:90-122` (§4, Redistribution): > 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works > thereof in any medium, with or without modifications, and in Source or Object form, provided > that You meet the following conditions: > (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and > (b) You must cause any modified files to carry prominent notices stating that You changed the > files; and > (c) You must retain, in the Source form of any Derivative Works that You distribute, all > copyright, patent, trademark, and attribution notices from the Source form of the Work, > excluding those notices that do not pertain to any part of the Derivative Works; and > (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative > Works that You distribute must include a readable copy of the attribution notices contained > within such NOTICE file … (there is **no NOTICE file** upstream, so (d) is vacuous here; > including an attribution file is still good practice). Trademark (`LICENSE:139-142`, §6): > 6. Trademarks. This License does not grant permission to use the trade names, trademarks, > service marks, or product names of the Licensor, except as required for reasonable and customary > use in describing the origin of the Work and reproducing the content of the NOTICE file. Warranty/liability disclaimers (`LICENSE:144-165`, §§7-8): "AS IS" basis, no warranty; no liability. Patent grant: §3 (`LICENSE:74-88`). Contribution terms: §5 (`LICENSE:131-137`). ### 1.3 Concrete obligations for the DSH port (a Derivative Work) 1. **Copy `LICENSE` verbatim** into the DSH plugin repo (e.g. `LICENSE` or `LICENSES/tokeneconomizer-LICENSE`), and state "Apache-2.0" in `package.json` + `README`. §2 grants the right to prepare Derivative Works; §4(a) requires shipping the license text to every recipient. 2. **Attribute the origin**, e.g. in `README` and in a `NOTICE`-style file: `Portions derived from microsoft/TokenEconomizer (https://github.com/microsoft/TokenEconomizer) at commit e428fc91b0f8ffecb5a596d56b14fe2f1485a5f9, Copyright (c) TokenEconomizer authors, licensed under Apache-2.0.` (Upstream has no per-file copyright headers and no NOTICE; the authors are named in `pyproject.toml:12` as "TokenEconomizer authors" and in the paper author list. This is not legal advice — if the port ships commercially, have counsel confirm the copyright-holder line; Microsoft's public Apache-2.0 release is the grant.) 3. **Modified-file notices** (§4(b)): since we are translating Python → JavaScript, *every* ported file is modified. Put a header comment in each ported source file, e.g. `// Ported from tokeneconomizer/microskills/envfacts.py (Apache-2.0, microsoft/TokenEconomizer @ e428fc9). Modified for the DSH port: .` 4. **State non-affiliation** exactly and prominently: "Not an official Microsoft product. This is an independent port of the mechanisms in microsoft/TokenEconomizer at commit e428fc9 (Apache-2.0); it is not affiliated with, endorsed by, or supported by Microsoft." Do **not** use "TokenEconomizer" as the DSH plugin's product name in a way that suggests it *is* the upstream product; a descriptive "port of TokenEconomizer" use is the kind of "reasonable and customary" description §6 allows. Upstream itself disclaims effect ("The plugin's own text … makes no claims of effect", `CLAUDE.md` conventions). 5. **Citation is not license-mandated.** Upstream asks for an academic citation in `README.md:370-384` (BibTeX for arXiv 2610.22732). Recommended in the port README: "If you use this, please also cite Li et al., TokenEconomizer, arXiv:2610.22732." 6. If we copy `trace/claude_tool_catalog.json` or other data files, carry the same attribution; the port does not need them (host-specific catalogs are dropped, see §10). --- ## 2. STATE MODEL (on-disk layout, identifiers, formats, atomicity) This section covers `plugin/paths.py`, `plugin/repos.py`, `plugin/store.py`, `plugin/context.py`, `plugin/fold.py`, `plugin/config.py`. A DSH port needs an equivalent state root and stores; the exact *file names* (`injection.json`, `untool.json`, `envelope.json`, ledger event kinds) are a compatibility surface upstream explicitly protects (`CLAUDE.md`: "On-disk state names … are a compatibility surface; renaming them strands what users have learned"). For a fresh DSH plugin we may pick our own root, but the *schemas* should be preserved so state/notes and tests translate. ### 2.1 Root and permissions - Root: `$TOKENECONOMIZER_HOME`, else `~/.tokeneconomizer`; always `os.path.abspath` (`plugin/paths.py:23-30`). DSH analogue: `$DSH_TOKENECONOMIZER_HOME` else `~/.dsh/tokeneconomizer` (or mirror `~/.tokeneconomizer` for drop-in familiarity; **decision needed by Lead** — recommended: `~/.dsh/tokeneconomizer` with the old env var also honoured). - `secure_root()` chmods the root to `0700` when it is owned by the user and group/other bits are set (`plugin/paths.py:33-46`); new files are created private via `umask 077` in the launcher. - `/bin/te` launcher (POSIX shell, chmod 0700, atomic replace; `plugin/paths.py:49-84`). **Not ported** (host CLI plumbing), but keep the idea of a private root. ### 2.2 Directory layout ``` / # TOKENECONOMIZER_HOME / ~/.tokeneconomizer ├── config.json # global settings (JSON, see §7) ├── index.json # repo index: checkouts seen, redirects, (see 2.4) ├── errors.log # append-only hook exceptions/tracebacks ├── bin/te # launcher (not ported) ├── sessions//.json # SESSION PINS (see 2.5) ├── merged/./ # whole state dirs set aside by migration └── repos// # one repository ├── config.json # repo-level settings (shared by all hosts) └── / # per-agent state ("envelope learned on one agent │ # says nothing about another's turn scale", │ # paths.py:12-16); host ∈ HOSTS ├── sessions/.json # per-session record written by hooks ├── folded.json # {sid: {at, transcript}} folded-once set ├── ledger.jsonl # one JSON object per folded session / overhead ├── events.jsonl # controller event log (status lines etc.) ├── injection.json # micro-skills: rendered block + facts (2.6) ├── untool.json # tool pruning evidence/proposal/applied (2.7) ├── envelope.json # loop guard per-repo turn envelope (2.8) ├── mechanisms/ # mechanism classes' own results state ├── fold.log / judge.log / repomap.log # detached-process logs └── .fold.lock .mechanisms.lock .state.lock .index.lock # lock files (2.9) ``` Citations: repo state dir `plugin/paths.py:251-253`; host dirs are `hosts.HOSTS` names (`plugin/repos.py:158-161`); `LiveContext.mech_dir` = `/mechanisms` (`plugin/context.py:23-26`); sessions dir/session record path (`plugin/controller.py:45`, `plugin/repos.py:9-10`); pin store `plugin/repos.py:69-70`; ledger/events/folded/settings writes (`plugin/fold.py:88-93,168-191,364-419`, `plugin/controller.py:75`); errors.log (`plugin/repos.py:58-65`); merged dir (`plugin/repos.py:448`). ### 2.3 Repository identity (the per-repo key) State is keyed by **repository, not directory**: one repo has one key however it is cloned (`plugin/paths.py:1-16`). - `repo_identity(cwd, env)` (`paths.py:229-248`): - find git checkout by walking up from cwd (`find_repo_root`, `paths.py:121-140`); honors `GIT_CEILING_DIRECTORIES`, treats `$HOME` as a ceiling; a `.git` dir counts only if it holds `HEAD`, a `.git` file counts only if its `gitdir:` target exists (`paths.py:109-118`). - If a remote exists: normalize it (`normalize_remote`, `paths.py:188-195`: strip scheme, user@, scp-style `host:path`→`host/path`, strip `.git`, lowercase) → `how="remote"`, `key = f"{_slug(name)}-{digest(remote)}"`, `label = "owner/repo"` (last two components). - Else if a checkout: realpath of main checkout (worktrees share the clone's `.git`) → `how="path"`; else the directory itself → `how="dir"`; `key = f"{_slug(basename)}-{digest(realpath)}"`, `label = basename`. - `_slug`: `re.sub(r"[^a-z0-9]+","-",text.lower()).strip("-")[:40] or "repo"` (`paths.py:198-199`); `digest`: `sha1(canon).hexdigest()[:10]` (`paths.py:202-203`). - `RepoId` fields: `root, key, label, remote, how` (frozen dataclass, `paths.py:210-216`); `how ∈ {"remote","path","dir"}` (`paths.py:207`). - **Session pin**: first hook of a session writes `/sessions//.json` = `{root,key,label,remote,how,cwd,at}` (`repos.py:44,105-107`); every later hook/fold uses the pinned repo even if the agent `cd`s elsewhere. Pin filename sanitized: `re.sub(r"[^A-Za-z0-9._-]","_",sid)[:200] + ".json"` (`repos.py:73-74`). Pins older than `PIN_MAX_AGE_S = 30*86400` pruned at most once/day (`repos.py:41,110-130`). - **Migration/legacy**: layout version `LAYOUT = 2` (`repos.py:40`); `index.json` remembers checkouts/redirects; pre-0.1.0 key = `f"{_slug(basename)}-{digest(remote or realpath)}"` where *any* `.git` entry counted (`repos.py:133-154`). Merges set whole dirs aside under `/merged/` and keep learned state as `premerge-*`; **nothing is deleted** (`repos.py:22-24`). Also: `toolprune` has its own `adopt` path for CLI-level checkout state (`repos.py:557-566`). For the DSH port: we can start at layout 1 with a fresh root, but implement the *same identity function* (remote-first) because it is cheap and gives multi-host sharing. ### 2.4 `index.json` (global) Read/written under `Store(root).locked("index")` (`repos.py:597-680`; `INDEX = "index.json"`, `repos.py:39`). Known schema fragments: - `{"redirects": {old_key: {"to": new_key, "at": ts}}}` — followed by `pinned()` with a max of 8 hops, only redirects newer than the pin (`repos.py:93-102`). - checkout index records of seen checkouts (used by `adopt`/`migrate`; exact field set is written by helpers in `repos.py:597-660` — **UNKNOWN**; see §11). - migration marker/`merged` count surfaced as `merged` in `te repos` output (`repos.py:835-844`). **Exact remaining schema is UNKNOWN** (lines `repos.py:597-680` were not fully read; the redirects map is confirmed). A DSH port can omit migration entirely (fresh root) but keep an `index.json` if we want `te repos`-style listing. ### 2.5 Per-session record written by hooks (`sessions/.json`) Written by the controller on hook events; fields read by fold: `transcript` (path to agent log), `started`, `ended`, and loop-guard counters `turns` (list), `nudges` (int), `aborts` (int), `judge_tokens`, `writer_tokens`, `injected_chars`, `injected_ids` (list of fact ids injected at session start), `folded` (bool) (`plugin/fold.py:368-372,406-422`). Controller's own doc (`plugin/controller.py:47-55`): `{"session_id","host","started","cwd","turns":[], "nudges":0,"aborts":0,"judge_tokens":0.0,"writer_tokens":0.0}` + `transcript`, `updated`, `source`, `injected_text`/`injected_chars`/`injected_ids`, `ended` (`controller.py:93-120`). DSH port: a session-state document per DSH session, written through a store with locking. ### 2.6 `injection.json` (micro-skills state, per host+repo) Written by `_fold_microskills` (`plugin/fold.py:88-93`): ```json { "profile": "off|envfacts|envfacts+distiller|distiller", "reason": "", "rendered": "", "rendered_ids": ["", ...], "facts": ["", ...], // srow["manual_facts"]; may be absent/null "updated": 1699999999.0, "sessions": 12 // monotonic count of folds } ``` `rendered` is what `stage.render()` produced; `rendered_ids` are `stage.injected_ids()` used to record which facts a session actually got (`fold.py:84-93`). ### 2.7 `untool.json` (tool pruning state, per host+repo) Generic host (Claude/Copilot) — `plugin/fold.py:168-191`: ```json { "used_tools": ["Read", "Bash", ...], // sorted list; every tool ever used "streaks": {"Notebook": 3, ...}, // consecutive sessions unused, per tool "prompt_streaks": {"Notebook": 27, ...}, // unused prompts accumulated "catalog_tools": ["Bash", ...], // sorted observed catalog "catalog_desc": {"Tool": "description", ...}, // tool -> description "catalog_source": "claude|codex|...|null", // where catalog came from "sessions": 7, // folds counted "prompts": 143, // prompts counted (None→1 per session) "updated": 1699999999.0, "proposal": ["Notebook", ...], // currently-proposed tools "new_in_proposal": [...], // proposal minus previous "servers": {...}, // copilot only: per-MCP-server summary "auto_pending": {"items": [...], "root": "", "since": ts}, // auto only "auto_applied": [...], // last write result (auto) "auto_apply_refused": "", // refused writes surface here "restored_protected": [...], // protected tools given back this fold } ``` Codex variant — `plugin/fold.py:262-272`: `{"sessions","catalog_source":"capabilities", "prompts","capabilities":{cap:{loaded_sessions,loaded_prompts,unused_sessions,used}}, "loaded_prompts":{cap:int},"proposal",...,"updated","new_in_proposal"}`. DSH port: the generic schema only (host-specific Codex capabilities dropped). ### 2.8 `envelope.json` (loop-guard per-repo turn envelope) Per host+repo; written by the loop guard (see §5, `plugin/controller.py` Stop path and `loopguard/envelope.py`); merge logic concatenates envelope samples and keeps `ENVELOPE_HISTORY` (`repos.py:326-331`: `merged[k] = (list(a.get(k) or []) + list(b.get(k) or []))[-ENVELOPE_HISTORY:]`). Schema (from `loopguard/envelope.py:157-191`): ```json {"level": "conservative|default|aggressive", "resolved_turns": [12, 40, 31, ...], // turn lengths of ended-green turns only, capped 500 "resolved_snaps": [{"turns","edit_churn","distinct_edit_files","error_outputs","test_runs", "stagnation","repeated_fail_sigs"}, ...]} ``` ### 2.9 JSON store, locking, atomic writes, corruption (`plugin/store.py`) - `Store(base)` = one directory of JSON docs + JSONL logs (`store.py:46-49`). - `read(name, default=None)` → `json.load`, returns `default` on `OSError`/`ValueError` (`store.py:60-65`) — **corrupt file = silently default**, caller re-initializes. - `write(name, obj)`: `os.makedirs(dirname)`, temp `f"{target}.{pid}.tmp"`, `json.dump(obj, indent=2, sort_keys=True)`, `os.replace(tmp,target)`; removes tmp on failure (`store.py:67-80`). - `append(name, record)`: JSONL, one `json.dumps(record, sort_keys=True)+"\n"` per line (`store.py:82-86`). `read_lines` skips malformed lines (tolerant), optional `limit` keeps last N (`store.py:88-100`). - `locked(name="state", timeout=10.0)`: opens `.{name}.lock`, polls non-blocking exclusive lock every 0.05 s, yields `got` bool; caller must check it when correctness matters (fold does); degrades to `yield True` when neither `fcntl` nor `msvcrt` is available ("a lost update is recoverable, a crashed hook is not", `store.py:102-130`). Locking is advisory; read-modify-write of shared JSON is wrapped in `with store.locked(...)`. - Hook contract: any exception in a hook → `{}` + a line in `/errors.log` (CLAUDE.md; `repos.py:58-65` shows the error writer). - DSH port: implement `Store` with `fs.promises` equivalents: atomic tmp+rename, JSONL append, a lock (proper-lockfile-style mkdir lock or `flock` via fs-ext — prefer a simple mkdir/`O_EXCL` lock with timeout, no native deps), tolerant reads. Node `JSON.stringify` with **sorted keys** to match upstream's `sort_keys=True` if byte-compat matters for fixtures. ### 2.10 Per-agent vs shared, cleanup/reset - **Per agent (host)**: `injection.json`, `untool.json`, `envelope.json`, `sessions/`, `folded.json`, `ledger.jsonl`, `mechanisms/` — under `/repos///` (`paths.py:12-16`). - **Shared across agents**: repository identity/key and repo-level `config.json` (at `/repos//config.json`), global `/config.json`. - **Cleanup**: pins pruned after 30 days (`repos.py:41,110-130`); `auto` pending list dropped when applied/refused/given back (`fold.py:312-341`). There is **no `te reset`/`te disable` command** upstream (verified in the parser `cli.py:705-758`); disabling is `TE_DISABLE=1` (`config.py:309-310`). Nothing else is deleted by the mechanisms; merges move dirs to `/merged/`. --- ## 3. MICRO-SKILLS Files: `microskills/observe.py` (session → classified observations/digest), `microskills/envfacts.py` (LLM-free fact families), `microskills/lifecycle.py` (state machine), `microskills/distiller.py` (optional paid proposer), `microskills/stage.py` (`PrefixStage`), `plugin/fold.py:_fold_microskills` (plugin persistence into `injection.json`). Pipeline: finished session log → `trace.load_trajectory` (host reader; DROPPED in DSH port, replaced by a DSH transcript adapter) → `observe.observe_task` → per-family discovery/verdict → `Lifecycle` → `render()` block cached in `injection.json` → injected at the next SessionStart (`plugin/controller.py:106-120`). ### 3.1 Command observation (`observe.py`, port THIS) Input: ordered `(tool_name, args, result_text, ok)` calls (any host schema normalized by `trace`). Only shell calls are classified: `tool in ("shell","bash","stop_bash")` (`observe.py:607`). 1. **Unwrap**: strip an agent's `/bin/bash -lc ""` wrapper (`unwrap`, `observe.py:167-179`). 2. **Segment**: heredocs are elided first (`_HEREDOC_RE`, `observe.py:124,185`), then split on `&& || ; |` and newlines **outside quotes**; each segment is `.strip().strip("()")` (`segments`, `observe.py:182-205`). This is a hand-written splitter, not `shlex` — no word expansion, no globbing, no subshell evaluation (deliberately conservative; see §8 risks). 3. **Per segment** (`classify_command`, `observe.py:292-367`): - strip leading `VAR=value` assignments (`_ENV_ASSIGN_RE`, `observe.py:121,313-317`; `env VAR=value cmd` too) and mark `assigned=True`; strip an rtk command prefix (`strip_rtk_prefix`, `observe.py:314-317`). - `cd DIR` updates the carried prefix `cd &&` and the tracked cwd (relative to session start; `_cd_target`, `observe.py:244-272`; a `cd` up to the repo top collapses to no `cd`; `~`/`$VAR`/`-`/absolute-unknown → session-relative state becomes unknown). - activation: `source …/activate X`, `conda|mamba activate X` → carried prefix `source activate X &&` / `conda activate X &&` (`observe.py:331-336`). - wrappers: `conda run -n X`, `poetry|pipenv|uv|npx|bundle|yarn|pnpm run|exec`, `npx` (`_CONDA_RUN_RE`, `_TOOL_RUN_RE`, `_NPX_RE`, `observe.py:116-119,339-351`). - strip prefix words `sudo env time exec nice nohup timeout` and stray assignments (`PREFIX_HEADS`, `observe.py:108,353-357`). 4. **Classify** head/base (`_classify`, `observe.py:381-446`) by the domain `Vocabulary`: - interpreter (`python*`, `node*`, plus versioned `python3.11` via `_INTERP_VERSIONED_RE`): only `--version/-V` → `probe_env`; `-m ` → `run_tests` (` -m ` form); a runner script argument (`runtests.py`, `bin/test`, `bin/doctest`, `manage.py` only with `test`) → `run_tests` (`