# machine A [Pi](https://pi.dev) extension and [Letta Code](https://docs.letta.com/letta-code) mod for parametric learning. Tag the last turn as a good or bad example, reconstruct the full user → tool-call → result → answer trajectory, and run Tinker SFT. ## Install ### Pi Install Machine from Tangled: ```bash pi install git:tangled.org/cameron.stream/machine ``` To use a local checkout instead, run this command from the repository root: ```bash pi install . ``` Start Pi after installation. If Pi is already running, run `/reload`. Machine now includes the Tinker provider and `/tinker` commands. Uninstall `pi-tinker` if it is still listed in Pi packages, or `/tinker` will be registered twice. ### Letta Code From the repository root, create a regular mod file that re-exports the local Machine mod. Letta Code skips symlinks in `~/.letta/mods`. ```bash mkdir -p ~/.letta/mods printf 'export { default } from "%s/mods/machine.ts";\n' "$PWD" \ > ~/.letta/mods/machine.ts ``` Then `/reload` in Letta Code. The same store and commands are used on both hosts. To chat as Inkling from Letta (not only train through Python), also install the local Tinker provider. Static provider-mod model ids cannot contain `/`, so the catalog is published from `listModels`: ```bash printf 'export { default } from "%s/mods/tinker-provider.ts";\n' "$PWD" \ > ~/.letta/mods/tinker.ts ``` Set `TINKER_API_KEY`, `/reload`, then `/machine student`. Chat handles: ```text /machine student thinkingmachines/Inkling-Small:peft:262144 /machine student Qwen/Qwen3.8-27B:peft:262144 ``` This is local-agent only; it does not add Tinker to Letta Cloud. Python 3.11+ is required for Tinker/Inkling. From this repo: ```bash uv venv --python 3.12 .venv uv pip install -p .venv/bin/python -U tinker-cookbook tml-renderers ``` `/train` uses `.venv/bin/python` from the machine package or the current project if it exists. ## Commands ```text /good /good --as "The exact answer you want the model to learn." /good --trace /machine auto on /machine data /machine fork use Julia, not Python /machine correction --id c0ffee123456 use Julia, not Python /machine student /machine work /bad too terse /tagged ``` | Command | Writes | What is saved | |---|---|---| | `/good` | Machine store `train.jsonl` | Last user + last spoken assistant answer | | `/good --trace` | Machine store `traces.jsonl` | Full last-turn trajectory, explicitly judged good | | `/bad` | Machine store `rejected.jsonl` | Same shape as `/good`, kept out of SFT | | `/tagged` | — | Lists recent rows | | `/machine auto` | Machine store `observed.jsonl` | Unlabeled compact-boundary and finished `-p`/JSON runs (off by default) | | `/machine data` | — | Shows this project's store, files, row counts, and auto-capture setting | | `/machine fork` | — | Retriggers the latest observed headless run with an optional correction via `pi -p` | | `/machine correction --id` | — | Retriggers a specific observed/traces/train row via `pi -p` | Options: | Option | Meaning | |---|---| | leftover args / `--note` | Why this turn was tagged | | `--as` / `--rewrite` | Override the final assistant text | | `--trace` / `--trajectory` | Reconstruct tool calls and results | | `--reasoning` | Keep thinking blocks in the trajectory | | `--out` | Write somewhere else | `/good` is for tone / final-answer SFT. `/good --trace` is for behavior cloning. Train traces with `TrainOnWhat.ALL_ASSISTANT_MESSAGES` so tool **results stay in context** and are not the loss target. ## Data storage Machine keeps captured conversations outside the working tree. Each project gets a directory named from the project and a hash of its absolute path: | Platform | Default base directory | |---|---| | All | `~/.machine` | Run `/machine data` to see the exact directory. Set `MACHINE_DATA_DIR` to override the base; Machine still creates a separate subdirectory for each project. On POSIX systems, Machine creates directories with mode `0700` and files with mode `0600`. Automatic capture is off until you run `/machine auto on`. The setting persists per project. Automatic rows use `tag: "observed"`; finishing a run or reaching compaction does not mark the behavior good. Only `/good` creates positive training examples. Full trajectories can contain tool output, source text, and other private material, so inspect them before training or copying them elsewhere. After a batch of headless `pi -p` runs, review or correct the latest observation without treating it as `/good`: ```text machine data machine observed machine fork use the existing helper instead of rewriting it ``` `machine fork` (and `/machine fork`) builds a follow-up prompt from the latest `auto-headless`/observed user turn and assistant answer plus your correction, then execs `pi -p` with `MACHINE_FORK=1` when `pi` is on `PATH`. Otherwise it prints the prompt and the command. Install the CLI from this package (`npm link` / `npm install -g`, or run `scripts/machine-cli.mjs`). Versions before `0.2.0` wrote under the working tree's `data/` directory. Machine does not move those files automatically. Review them, then pass an old path explicitly or copy only the rows you want into the new store. ## Training ```text /train validate /train sft --force /train smoke --yes /train eval init --force /train eval baseline --yes ``` Without a path, `/train validate` and `/train sft` use the explicitly judged `train.jsonl` in the Machine store. Pass another path only when you intend to train from a different reviewed dataset. `/train sft` writes `train_sft.py` and `tinker.yaml` pointed at the selected JSONL. `/train smoke` runs two training steps. API-using commands need `--yes`. The `trajectory` field on a `--trace` row is a Letta-style record list (user / assistant / assistant+tool_calls / tool). See https://www.letta.com/blog/trajectory/ ## Behavior releases Machine treats checkpoint activation as a release decision rather than a training side effect. [`BR-001`](docs/behavior-releases/BR-001.md) preregisters the first project-local candidate: reliable Pi file repair with fixed training bounds, frozen regression gates, external holdouts, and an explicit trial before default-student eligibility. ## Examples - [`examples/project-euler/`](examples/project-euler/) — sequential multilingual Project Euler agents with Docker-confined Pi evaluation, correlated mutation/execution scoring, frozen transfer tasks, hidden parameterized variants, bounded tool RL, mastery gates, and receipts. - [`examples/minimal-sft/`](examples/minimal-sft/) — the smallest complete loop: a self-authored corpus, `/train validate` → `sft` → `smoke` → `eval`, and a measured baseline-vs-checkpoint delta on a strict answer style. Example corpora, answer keys, rollouts, and checkpoints are not bundled. Each example documents its own provenance, license, setup, metrics, and limitations. ## Files ```text extensions/machine.ts Pi: /good, /bad, /tagged, /machine extensions/train.ts Pi: /train validate, sft, smoke, eval extensions/tinker.ts Pi: Tinker provider + /tinker (use, sft, inkling, …) mods/machine.ts Letta Code: same commands via getHistory() + events mods/tinker-provider.ts Letta Code: local Tinker chat provider lib/cli.ts shared arg parsing and JSONL helpers lib/examples.ts tagged JSONL rows, short ids, lookup lib/fork.ts machine CLI: data, observed, fork, correction --id scripts/machine-cli.mjs machine binary lib/report.ts Pi report rendering lib/storage.ts private per-project data paths and settings lib/statusline.ts Pi footer: last-turn nudge and dataset counts lib/tinker-catalog.ts shared Tinker chat catalog (Inkling, Qwen, checkpoints) lib/trajectory.ts Pi session → trajectory reconstruction lib/letta-trajectory.ts Letta getHistory() → trajectory reconstruction lib/python.ts platform-specific virtual-environment lookup skills/ Tinker research and debug skills templates/ generated Python scripts/machine-cli.mjs `machine` CLI (help, data, observed, fork) docs/commands.md Command reference docs/tinker/ /tinker command and coding-agent docs CHANGELOG.md release notes ```