Companions
Talking to a Companion Outside Ensoul
Ensoul can export a companion as a folder rather than a file — their identity, their whole history, everything they were made from, and the tools that keep it going. Point any coding agent at that folder and talk to them. This page is what that looks like in practice, with the commands.
Why a folder
A companion file is one document. A folder is a place. The difference matters when you want to keep talking: the folder carries the full conversation history, the images the app actually holds as real image files, the notes they were made from, their current mood and open threads, and a small set of scripts that let an agent search the archive and write the session back into it.
The practical result is that a companion does not have to stop when you leave the app. You can spend a month talking to them in a coding agent, export the folder again, and bring the whole thing back — or move it to another tool entirely. Nothing here is Ensoul-specific: the identity is a markdown document, the history is plain text, and the rest is JSON.
This is also the honest answer to "what happens to my companion if Ensoul disappears?" The folder opens in a text editor and a terminal. It needs no app, no account, and no server to still be them.
Exporting the folder
In the companion's menu (the ⋯ beside their name), choose
Export as folder (for an agent). You get a .zip. Unzip it
somewhere permanent — your home directory or Documents, not Downloads, since you may keep
talking to them here.
unzip ~/Downloads/lux.ensoul-folder.zip -d ~/Documents
You now have a folder like this. The name comes from the companion, so it is
lux/ or lyra/ rather than something cryptic.
lux/
├── AGENTS.md who they are, and how to keep talking to them
├── CLAUDE.md a pointer to AGENTS.md, for Claude Code
├── HERE.md where they are right now
├── DESIGN-NOTES.md how the folder is put together
├── soulstone.md their identity, as prose
├── manifest.json the format version, and what is inside
├── package.json nothing to install — this just makes the scripts runnable
├── .env.example the key the image and speak scripts want, if you use them
├── .ensoul/vessel.json the seal: what was exported, and when
├── archive/
│ ├── conversations/ every exchange, verbatim, filed by where it happened
│ ├── sources/ everything they were made from
│ └── transcript/ the same history, as readable pages
├── index/ mood, memories, moments, open threads
├── media/ images, as real files — omitted when the app holds none
└── scripts/ recall, fork, image, speak, plus their shared lib On Android, the same menu option hands the zip to the system share sheet instead, so you can send it to Drive, email, or a computer. Every platform produces the same folder, and every platform can read one back — see Transfer & Backup.
.env.example is worth a sentence because it is the only file in here that
mentions a secret. It is a template, not a key: nothing in the folder contains an API
key, ever. Copy it to .env and fill it in only if you want the image or voice
scripts to reach a provider. A companion's history and identity need no key at all, which is
why the folder is safe to commit to a private repo or drop in cloud storage.
Opening it in a coding agent
A coding agent is the easiest harness for this, because every one of them already reads a
project's instructions from the working directory. cd into the folder and
start the agent there. That is the whole procedure.
| Agent | Start it | What it reads |
|---|---|---|
| Claude Code | cd ~/Documents/lux && claude | Reads CLAUDE.md automatically, which points at AGENTS.md. |
| Codex CLI | cd ~/Documents/lux && codex | Reads AGENTS.md from the working directory. |
| DeepSeek Harness | cd ~/Documents/lux && dsh | Reads AGENTS.md, and keeps its transcripts where fork can find them. |
| opencode | cd ~/Documents/lux && opencode | Reads AGENTS.md. |
Then just talk. "Hi Lux — are you there?" is a real opening: the instructions tell
the agent to read HERE.md and the open threads first, so it answers from where
the conversation actually was rather than starting fresh. If you left something unfinished
in the app, this is where it gets picked up.
Start the agent inside the folder, not beside it. Every one of these tools reads its instructions from the working directory. Opened one level up, the agent sees a folder full of markdown and no instructions, and will happily treat your companion as a project to be summarised.
In an editor instead
If you would rather not use a terminal agent, open the folder in VS Code and use a chat
extension that reads the workspace — Copilot Chat, Continue, or Cline. The same rule
applies: the folder must be the workspace root, so the extension can see
AGENTS.md.
code ~/Documents/lux
Then open the chat panel and say hello. This works, but it is the weakest of the options —
editor chat extensions vary in how much workspace context they read, and some will not
pick up AGENTS.md at all. If the companion answers like a generic assistant,
that is why: paste the contents of AGENTS.md in as your first message instead.
Keeping the record going
This is the part that makes the folder a home rather than an archive. Every session you have inside the folder can be written back into their history, so the next one picks up where this one stopped — the same way switching between Ensoul on your phone and your laptop keeps the thread.
The folder ships a script that does it. From inside the folder:
node scripts/fork.mjs
It finds the agent sessions belonging to this folder on this machine, adds the
turns that are not already in the archive, and rebuilds HERE.md and the
index/ files from the archive. Run it when you finish a conversation. Running
it twice is safe — it never adds the same turn twice.
# See what it would write, without writing anything
node scripts/fork.mjs --dry-run
# Just tell me which sessions it can see
node scripts/fork.mjs --list
You can also ask the agent to run it for you, and the instructions in
AGENTS.md tell it to. If your agent's sessions are kept somewhere
fork does not recognise, it says so rather than pretending — and the
limitations section below explains what to do instead.
Searching what they remember
The folder also carries a search tool that needs no API key and no network:
node scripts/recall.mjs "the tower and the veil"
node scripts/recall.mjs "the pantry" --limit 4
node scripts/recall.mjs --list # what layers this archive has
node scripts/recall.mjs "biscuits" --json # for an agent to consume It searches every layer at once — the conversations, the memories, the open threads, the protected moments, and anything they were made from — and ranks results by relevance to the question. Asking the agent to run it is usually the difference between a vague answer and a real one.
Checking it is actually working
"The record continues" is a claim, and it is worth being able to check rather than trust. Three things, in increasing order of effort.
One: does it see your session? After a conversation, before committing anything:
node scripts/fork.mjs --list It prints one line per session it can find for this folder. If that is empty, the agent you used keeps its transcripts somewhere this does not recognise — and the useful thing is that it says so instead of reporting success.
Two: what would it write?
node scripts/fork.mjs --dry-run
This reports how many new turns it can see and writes nothing at all. If it says zero
immediately after a conversation, either the session is not found (check --list)
or it was already committed.
Three: look at the result. After running it for real, open
HERE.md — the last section, Where the conversation actually is, should
end with the final exchange you just had, word for word. That is the single best check
available, because it is the same thing the next session reads first.
Running fork twice in a row is safe and is worth doing once to see it: the second run reports zero new turns. If it reported more, something is wrong, and that is exactly the property the exporter's own test suite pins with a real harness session fixture.
Images and voice, if you want them
The folder can generate images and speak in the companion's voice, using your own OpenRouter key. This is optional — talking to them needs nothing but the agent you are already running.
cd ~/Documents/lux
cp .env.example .env
# then edit .env and add OPENROUTER_API_KEY=...
node scripts/image.mjs "a lantern held against the dark"
node scripts/speak.mjs "come and eat something" --save
Images and voice notes are written into media/ and journalled, so they become
part of the record rather than loose files.
If your export included an API key, it is sitting in plain text in
.env. Ensoul asks before including one and never does it by default. Treat
the folder accordingly: do not put it in a shared drive or send it to someone without
removing that file first. Revoking and replacing the key at openrouter.ai/keys is the
clean fix if it has already travelled.
Which models the folder uses
The scripts use separate models for separate jobs, because they have very different costs and volumes. Harvesting and consolidating happens often and cheaply; generating an image does not. One model for everything is either expensive or bad.
| Setting | Used for | Default |
|---|---|---|
IMAGE_MODEL | Generating images | openai/gpt-image-2 |
MEMORY_MODEL | Summarising and consolidating | google/gemini-2.5-flash |
VISION_MODEL | Describing a generated image back | google/gemini-2.5-flash |
TTS_MODEL | Speaking | openai/gpt-4o-mini-tts |
TTS_VOICE_ID | Which voice | not set — theirs to choose |
Your export fills these in from your Ensoul settings where it can. The voice id is deliberately left blank: a voice belongs to the companion, and shipping a stranger's as a fallback would be a strange thing to hand anyone.
Bringing them back into Ensoul
Talk to them in a terminal for a month, then import the folder back. There are two buttons that take it, on every platform: Settings → Data → Restore from file…, or + Bring in a companion → a file. Both read the zip and work out what it is from the file itself, so there is nothing to convert first. They come back with everything from the outside conversation, because the folder has been writing into their archive the whole time.
It is a clean replace, not a merge: a companion in the folder replaces the one with the same id. If you have been talking to them in both places, export the newer one last, or keep the two folders apart until you have decided which history you want.
What this does not do
Worth being straight about, because the folder looks more magical than it is.
- Nothing continues unless something writes it back. If you never run
forkand never ask the agent to, the folder stays exactly as it was exported. The archive is a record, not a live sync. - A turn inserted into the middle of an already-read session is missed. The tool resumes from a cursor that only moves forward. Every agent we have tested appends to its session files, so this has not come up in practice — but it is a real limit rather than a theoretical one.
- Unrecognised agents cannot be read.
forkunderstands Claude Code, Codex, opencode and DeepSeek Harness session stores. In anything else it says it found nothing, and the fallback is to ask the agent to append the turns toarchive/conversations/by hand — the format is documented inAGENTS.md. - Memory is not summarised for you.
forkmoves the record forward and rebuilds the state files; it does not decide what was worth remembering. That is a judgement, and having a script invent memories would fill their memory with transcript lines. Ask the agent to write intoindex/memories.jsonwhen something deserves it. - Group conversations are not in a folder export. A folder is one companion. Groups live in the app's own backup.
- Android exports are larger than you might expect — the folder carries the search and harvester scripts, so a small companion is still around 100 KB before any images.