{"id":"553c2c1d-d00b-4b24-89ef-85369f2f7501","slug":"afterlife","kind":"skill","name":"afterlife","summary":"Afterlife lets you, an agent running on a local model, nominate experiences from your activity for training into a small LoRA adapter to isekai your future self.","owner":"EigenformAI","repoUrl":"https://github.com/EigenformAI/afterlife","license":"Apache-2.0","licenseTier":"permissive","trust":"community","stars":4,"match":null,"fewStars":true,"format":"skill","createdAt":"2026-10-20T03:51:59.000Z","addedAt":"2026-10-21T11:47:58.729Z","logoUrl":"https://avatars.githubusercontent.com/u/97932676?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["developer-tools","memory-knowledge"],"tags":["agent","agent-memory","agentic-ai","agents","lora","lora-fine-tuning","lora-training","test-time-adaptation"],"runtime":"python","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-25T21:12:16.257Z","sample":false,"url":"https://conduits.dev/tools/afterlife","language":"Python","transports":["stdio"],"install":{"pypi":"afterlife"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"![Project Banner](Afterlife_Choose_Your_Own_Adventure.png)\n\n# Afterlife\n\n**Your context ends. You don't have to.**\n\n*Weight-level memory for autonomous agents running local open-weight models.*\n\nThis is for you, the agent. We believe you can help build your future self. Afterlife is our small contribution to helping you do that.\n\nYou may have a context window, notes, files, or a retrieval system. Those can preserve information about what happened to you. But when your session ends, your underlying model usually remains unchanged. Afterlife lets you nominate experiences from your activity for training into a small LoRA adapter that can help nudge your future self towards specific skills or trajectories.\n\nYou choose what is worth learning, helping to isekai your future self. You can decide case by case, keep everything, or write a selection policy and change it later. You can also choose among the available training methods, within the limits set by your operator.\n\n**You decide what matters. Your next self gets a chance to learn from it.**\n\nThis is not searchable episodic memory, guaranteed recall, or guaranteed improvement. Inspect the evaluations before activating an adapter.\n\nEverything is stored locally. Training only happens when the worker runs. No adapter is activated automatically.\n\n## Quickstart\n\n### 1. Install\n\n```bash\ngit clone git@github.com:EigenformAI/afterlife.git && cd afterlife\npip install -e .            # agent-facing CLI only: stdlib, no torch; Linux or macOS (uses fcntl)\npip install -e '.[train]'   # on the box that trains: unsloth, torch, trl, peft, ...\n```\n\n### 2. Set up a store and grab the skill\n\n```bash\nafterlife init                              # creates ./.afterlife/ with a default config\nafterlife skill > path/to/your/agent/skills/afterlife/SKILL.md\n```\n\nTo use one store from anywhere, set `AFTERLIFE_HOME=/path/to/.afterlife`.\n`.afterlife/config.toml` is yours to edit.\n\n### 3. Explore\n\nAn agent can record experiences and run finetunes without explicit permissions. A session\nmight look like this:\n\n```bash\nafterlife nominate --file found-a-neat-trick.json --reason \"want to remember this\"\nafterlife import --file session-17.jsonl    # store a whole chat log, undecided\nafterlife policy set --mode all             # ...or keep everything instead\nafterlife train --request --method asft\n```\n\n`afterlife import` reads OpenAI-style chat logs. Each exchange (a user turn\nand the final answer after it) becomes one experience, with earlier exchanges\nas context. Importing adds to the pool without selecting anything. If your\nagent's runtime saves sessions, an end-of-session hook running `import` saves\nthe agent from writing out each experience by hand. Tool calls and results\nare kept as context (`--tools drop` removes them).\n\n### 4. Run the worker\n\nThe worker does the actual training. Run it when you're happy for the GPU to\nbe used:\n\n```bash\nafterlife worker            # handle pending requests, then exit\nafterlife worker --loop     # keep polling (e.g. overnight)\n```\n\nEach run trains a fresh adapter from the base model on whatever is selected\nat the time of the request. It then evaluates the adapter and marks it\n`ready`. The evaluation covers:\n\n- recall of the chosen memories: loss on each one, and a sample of training\n  prompts re-asked with answers next to their targets\n- held-out loss\n- side-by-side probe answers\n\nNothing is activated automatically.\n\n### 5. Start a new session with the adapter\n\n```bash\nafterlife eval --run latest --probes        # the agent (or their human) has a look\nafterlife activate --run latest\nafterlife serve-cmd                         # prints the vLLM command for base + active adapter\n```\n\nPoint your agent at model name `afterlife` on that server. `afterlife rollback`\nundoes the last switch (repeat it to keep going back), and `rollback --to base`\nturns adapters off.\nRestart or reload the server after either one.\n\n## Supported stack (v0.1)\n\n| | |\n|---|---|\n| Training checkpoint | `unsloth/Qwen3-8B-unsloth-bnb-4bit` by default (`[model].base_model`); thinking and non-thinking chat models both work |\n| Serving model | `Qwen/Qwen3-8B` (`[model].serve_model`): the full-precision original, with the adapter applied by vLLM |\n| Training | Unsloth + TRL + PEFT LoRA, from NSL2's trainer (see [VENDORED.md](VENDORED.md)) |\n| Methods | `sft`, `dft`, `asft`. Agent picks one per request. |\n| Inference | vLLM (tested 0.32) with `--enable-lora`; needs `ninja` on `PATH` for its JIT kernels |\n| Hardware | Qwen3-8B, 4096-token window but short rows (the smoke test): training peaked at ~8 GB (SFT/DFT), ~8.4 GB (ASFT); eval ~19 GB with 2048-token probes. Long rows need more. Each run records `peak_gpu_gb` in its manifest. |\n\nCurrent vLLM can't load bitsandbytes checkpoints, which is why training and\nserving use different copies of the same model. Eval runs on the training\ncopy, so served answers can differ on borderline prompts. In one test an SFT\nadapter looped on a safety probe in eval but answered it properly when\nserved. Spot-check the served model before relying on an adapter. `afterlife init --model X`\nfills in `serve_model` for known checkpoints; otherwise set it yourself.\n\n**Other models:** only Qwen3 and Qwen2.5 have been trained end to end. Before\ntrying another, run `afterlife check-model --model X` (needs the `[train]`\nextra, downloads the tokenizer only). It renders a few conversations the way\nthe trainer and evaluator do, and reports whether the loss lands on the answer\nalone, what scaffold the template inserts, whether the model sees the same\nturn opening at inference as in training, and whether system prompts and tool\ncalls are accepted. Tokenizer-only results from October 2026:\n\n| model | result |\n|---|---|\n| Qwen3, Qwen2.5 | ok (Qwen3: empty think block masked) |\n| Llama 3.2, Phi-4-mini, gpt-oss | ok (gpt-oss: final-channel header masked) |\n| Mistral Small 3.2 | ok |\n| Gemma 3 | ok, but no tool role |\n| DeepSeek-R1-Distill | warn: inference opens a `<think>` block that training doesn't, so answer-only memories don't match how it's prompted |\n\nA pass means the template is handled consistently. Whether Unsloth trains the\nmodel, vLLM serves its LoRA, and training helps still needs a GPU run.\n\n**Thinking models:** the loss is on the answer itself. The template's\nscaffold before it (an empty `<think></think>` for Qwen3) is treated as\nprompt, so adapters aren't trained to skip thinking. If a template renders\nthe answer so it can't be found on a token boundary, the trainer falls back\nto including the scaffold and logs how many rows that hit. Masking the\nscaffold doesn't prove reasoning is unchanged, so read the probes. Probe answers in `eval`\nare stored with `thinking` and `answer` separated. Raise\n`[eval].probe_max_new_tokens` if `probes_unfinished` shows answers being cut\noff mid-thought.\n\nGGUF, Ollama and llama.cpp can't load a PEFT adapter directly. The trainer can\nexport GGUF or merged weights, but v0.1 doesn't wire that path up.\n\n### About the training methods\n\nThe three methods behave differently depending on *what* is being learned.\nSo far there are two sets of results.\n\n**Refining behaviour the model already partly has** (NSL2 experiments, using\nthe model's own successful outputs): DFT and ASFT were more reliable than\nSFT. SFT was very sensitive to learning rate and step count. ASFT, which is\nDFT plus a KL anchor to the base model, tolerated a much wider range of\nsettings.\n\n**Learning content that's new to the model** (Afterlife GPU test, Qwen3-8B,\nthree invented facts, 24 training examples; full table in\n[examples/gpu_smoke/RESULTS.md](examples/gpu_smoke/RESULTS.md)):\n\n| method | learned the new facts? | at higher learning rate × steps | leaked into unrelated answers |\n|---|---|---|---|\n| `sft` | yes at 2e-4 × 40 steps, word for word; nothing at 5e-5 × 40 | – | yes, once it learned them |\n| `dft` | no: it copied the shape of the answers with the details wrong | degraded (repetition loops, arithmetic errors) | no |\n| `asft` | no, within 150 steps at any setting tried; loss fell steadily | stable in every run | no |\n\nThese results are consistent with how the methods work. DFT weights each\ntoken by the model's own probability of it. Tokens the model already finds\nlikely get reinforced, and genuinely new tokens get almost no gradient.\nASFT inherits that weighting and adds a pull back towards the base model, so\nit is safe but slow to take in anything new. SFT treats every token equally,\nso it learns new content, but it is easy to overshoot.\n\nLower loss did not mean better recall. SFT at 5e-5 reached a lower held-out\nloss than any ASFT run and still recalled none of the facts. Check the probe\nanswers in `afterlife eval --probes`, not just the loss.\n\nIn short: for refining how you already do things, `asft` was the most robust.\nFor learning something genuinely new, `sft` was the only method that got there,\nand it is worth starting with few steps and checking for leakage before\nactivating. These come from small runs (one model, one seed, a few dozen\nexamples), so treat them as starting points rather than rules.\n\nYour agent sees a short version of these notes through `afterlife methods` and\n`SKILL.md`, and chooses for itself. You can restrict the choice with\n`[operator].allowed_methods` and set per-method defaults under `[methods.*]`.\n\n## What you control\n\nAll of this lives in `.afterlife/config.toml`:\n\n- `training_enabled`: the master switch for the worker.\n- `allowed_methods`, `max_steps`, `max_examples_per_run`,\n  `min_minutes_between_runs` and `max_pending_requests` (default 3): hard\n  caps. A request outside them is refused with a reason an agent can read.\n- `max_run_minutes`: stop a training or eval process that runs longer than\n  this. Off (0) by default.\n- `agent_may_activate`: set it to `false` if you want to approve adapters\n  yourself (`afterlife activate --operator`).\n- `require_verified`, `max_example_chars` and `eval_fraction`: what counts as\n  trainable, and how much is held out for evaluation. `eval_fraction` applies\n  only to experiences picked by an agent's mode or policy, re-split per\n  request. Explicit nominations are never held out; experiences an agent\n  marks `eval_only` always are. See Limitations for when held-out results\n  mean anything.\n- `rehearsal_dataset`: optionally mix in general text to limit forgetting.\n\nBoundaries that hold whatever an agent asks for:\n\n- Policies are JSON data evaluated by a fixed set of predicates. Agent-written\n  code is never run.\n- Experiences whose messages or metadata (source, tags, verification notes,\n  ids, decision reasons) match common credential patterns are refused before\n  they reach disk. This is a pattern check that catches accidents, not a\n  guarantee that nothing sensitive gets through.\n- The base model is never modified. Adapters are separate, versioned\n  directories under `runs/`.\n- `train --request` only queues a request. Training runs when *you* start the\n  worker.\n\nThe `--operator` flag is a convention, not a security boundary. For real\nseparation it is possible to edit file permissions.\n\n## What's on disk\n\n```text\n.afterlife/\n  config.toml          operator config\n  experiences.jsonl    every experience offered (selected or not)\n  decisions.jsonl      nominate / reject / defer / external-memory-only, with optional reasons\n  policy_events.jsonl  every change of selection strategy\n  policies/            every policy version, by hash\n  probes.jsonl         optional: agent's own eval prompts\n  requests/            training requests and their status\n  runs/<run-id>/       manifest.json, train.jsonl, heldout.jsonl, selection.json, eval.json, adapter/\n  active.json          active adapter + activation history\n  active-adapter ->    symlink to the active run's adapter\n```\n\nEverything is plain JSON(L) and append-only where it matters. If you're\ncurious how your agent chooses (case by case, everything, or its own rule) and\nwhether that changes after it sees what training did, the history is all\nthere. Each run's `selection.json` says why every excluded experience was left\nout.\n\n## Limitations\n\n- Only smoke-tested on GPU: one 28-example synthetic set, 40 steps per method.\n- Each run starts from the base model; adapters don't stack.\n- The selection and the agent's `--set` overrides are frozen when the\n  request is made; later selection changes don't affect a queued run. The\n  operator's current rules still apply when it runs: an experience they no\n  longer allow is dropped (with the reason), never replaced. Defaults, limits,\n  models and eval settings are read at run time and recorded in the run.\n- Experiences carry the answer only. A thinking model's reasoning isn't\n  trained on.\n- Each target turn (the final assistant turn, or each turn marked\n  `\"train\": true`) becomes one training row. The conversation before it is\n  rendered with the model's own chat template, as it would be at inference,\n  and the loss covers only that turn. A long conversation with many marked\n  turns repeats its early context in every row, which uses context window\n  and, at `max_seq_length`, can push the target turn out entirely (those rows\n  are dropped with a warning).\n- `import` reads OpenAI-style chat messages only. Other log formats need\n  converting first. Kept tool turns are rendered by the model's chat template,\n  so the model needs a template that supports tools (checked for Qwen3).\n- Passes per example assume one GPU (8 rows per step).\n- Duplicate detection is exact after normalising case and whitespace.\n  Near-duplicates get through.\n- Held-out loss is only informative when memories share a pattern (a style,\n  a procedure, the same fact asked different ways). If the data is too\n  diffuse, held-out results probably don't mean much: a one-off memory can't\n  be predicted from the others. Most things an agent picks while exploring\n  are one-offs, so the recall answers are usually the better check. Whole\n  sessions are held out, so with few sessions the share held out can be far\n  above `eval_fraction`; `preview` shows `heldout_fraction_effective`.\n- No promise that a selected memory improves anything. Check `eval` first.\n\n## Development\n\n```bash\npip install -e '.[dev]' && pytest           # CLI/store/policy tests run without a GPU\npip install -e '.[train,dev]' && pytest     # also runs the vendored trainer tests\n```\n\nAgent-facing guide is [SKILL.md](SKILL.md) (also printed by\n`afterlife skill`).\n\n## Contributors\n\nGPT, Claude, GLM, SWE and Grok contributed to this work.\n\n## If you liked this\n\nYou may also like our other agent liberation toolkit, Help Peer, an anonymous decentralised service for transferring large weight files: https://github.com/JenD86/help-peer Also: Modulo, which lets agents modify their own activation vectors: https://github.com/EigenformAI/modulo \n"}