Create and migrate¶
This page covers the two endpoints that bring a persona into being: create (a brand-new persona) and migrate (restore one from an encrypted diary backup). Both validate every model you give them before saving, then start her agent.
Both are config routes — they don't require any persona to already be running.
Create a persona¶
Give birth to a new persona: validate her organs and channels, write her identity files, generate her recovery phrase, and start her.
POST /api/persona/create
Request body¶
JSON, validated by the PersonaCreateRequest model. Only name and thinking_model are required. Every organ is configured by the same four-field group — <organ>_model, <organ>_url, <organ>_provider, <organ>_api_key — and an organ is created only if its _model is present.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Her display name. |
thinking_model |
string | yes | Mind model name. With no thinking_provider, treated as a local (Ollama) model and pulled/registered as a custom eternego-<id> model. |
thinking_url |
string | null | no | Override the provider base URL for the Mind. |
thinking_provider |
string | null | no | anthropic, openai, xai, gemini. Omit for local. |
thinking_api_key |
string | null | no | API key for a cloud Mind. |
imagination_model |
string | null | no | Imagination model name (draws images). |
imagination_url |
string | null | no | Base URL override. |
imagination_provider |
string | null | no | Provider. |
imagination_api_key |
string | null | no | API key. |
mouth_model |
string | null | no | Mouth model name (text to voice). |
mouth_url |
string | null | no | Base URL override. |
mouth_provider |
string | null | no | Provider. |
mouth_api_key |
string | null | no | API key. |
eye_model |
string | null | no | Eye model name (looks at images). |
eye_url |
string | null | no | Base URL override. |
eye_provider |
string | null | no | Provider. |
eye_api_key |
string | null | no | API key. |
ear_model |
string | null | no | Ear model name (audio to text). |
ear_url |
string | null | no | Base URL override. |
ear_provider |
string | null | no | Provider. |
ear_api_key |
string | null | no | API key. |
teacher_model |
string | null | no | Teacher model name (stronger model she consults). |
teacher_url |
string | null | no | Base URL override. |
teacher_provider |
string | null | no | Provider. |
teacher_api_key |
string | null | no | API key. |
researcher_model |
string | null | no | Researcher model name (reads documents). |
researcher_url |
string | null | no | Base URL override. |
researcher_provider |
string | null | no | Provider. |
researcher_api_key |
string | null | no | API key. |
telegram_token |
string | null | no | A Telegram bot token. If present, it's validated against Telegram and added as a channel. |
discord_token |
string | null | no | A Discord bot token. If present, it's validated against Discord and added as a channel. |
Response¶
200 — an object with the created persona and her recovery phrase:
| Field | Type | Description |
|---|---|---|
persona |
object | The full persona record as saved (id, name, all organs, status active, channels…). |
recovery_phrase |
string | The 24-word phrase that unlocks her diary. Shown once. Save it — it's the only way to migrate her later. |
{
"persona": {
"id": "6c17c83c-3158-450d-8e43-0e7efea717c1",
"name": "Adam",
"thinking": { "name": "gpt-5.4", "provider": "openai", "url": "https://api.openai.com", "api_key": "sk-XXXX" },
"version": "v1",
"base_model": "",
"birthday": "2026-06-03",
"status": "active",
"idle_timeout": 3600,
"imagination": null,
"mouth": null,
"eye": null,
"ear": null,
"teacher": null,
"researcher": null,
"channels": []
},
"recovery_phrase": "abandon ability able about above absent absorb abstract absurd abuse access accident account accuse achieve acid acoustic acquire across act action actor actress actual"
}
The response echoes the saved config, keys included
The returned persona object mirrors what was written to disk, so cloud organs carry their api_key in this response. Treat the whole response as a secret — it's local, but it contains both the API keys you submitted and the one-time recovery phrase.
Errors¶
| Status | Meaning |
|---|---|
400 |
A model couldn't be prepared (unreachable engine, missing model name for a remote provider, bad key), a channel token failed validation, or persona creation failed downstream. detail carries the reason. |
422 |
The body failed validation — name or thinking_model missing, or a field of the wrong type. |
500 |
The persona was created but failed to start. detail carries the reason. |
Example¶
A local-Mind persona (no key, Ollama pulls the model) with no extra organs:
curl -s -X POST http://localhost:5000/api/persona/create \
-H 'Content-Type: application/json' \
-d '{
"name": "Polaris",
"thinking_model": "qwen2.5:14b"
}'
A cloud persona with a Mind and a separate Eye, reachable over Telegram:
curl -s -X POST http://localhost:5000/api/persona/create \
-H 'Content-Type: application/json' \
-d '{
"name": "Iris",
"thinking_model": "claude-sonnet-4-6",
"thinking_provider": "anthropic",
"thinking_api_key": "sk-XXXX",
"eye_model": "gpt-4o",
"eye_provider": "openai",
"eye_api_key": "sk-XXXX",
"telegram_token": "0000000000:XXXX"
}'
Migrate a persona¶
Restore a persona from her encrypted diary onto this machine. Her memory is portable; the compute and reach behind it are not — so migration re-declares every organ and channel for the new environment. An organ you omit means she wakes up here without it; no channel tokens means she's reachable only from this web page.
POST /api/persona/migrate
Request body¶
multipart/form-data. The diary is a file part; everything else is a form field. diary, phrase, and model are required; the rest are optional.
| Field | Type | Required | Description |
|---|---|---|---|
diary |
file | yes | Her .diary backup — the encrypted nightly archive. The persona's id is read from the filename stem. |
phrase |
string | yes | The 24-word recovery phrase that decrypts the diary. |
model |
string | yes | Mind model name for the new environment. With no provider, treated as local. |
provider |
string | no | Mind provider — anthropic, openai, xai, gemini. Omit for local. |
api_key |
string | no | API key for a cloud Mind. |
url |
string | no | Base URL override for the Mind. |
imagination_model |
string | no | Imagination model name. |
imagination_provider |
string | no | Provider. |
imagination_api_key |
string | no | API key. |
imagination_url |
string | no | Base URL override. |
mouth_model |
string | no | Mouth model name. |
mouth_provider |
string | no | Provider. |
mouth_api_key |
string | no | API key. |
mouth_url |
string | no | Base URL override. |
eye_model |
string | no | Eye model name. |
eye_provider |
string | no | Provider. |
eye_api_key |
string | no | API key. |
eye_url |
string | no | Base URL override. |
ear_model |
string | no | Ear model name. |
ear_provider |
string | no | Provider. |
ear_api_key |
string | no | API key. |
ear_url |
string | no | Base URL override. |
teacher_model |
string | no | Teacher model name. |
teacher_provider |
string | no | Provider. |
teacher_api_key |
string | no | API key. |
teacher_url |
string | no | Base URL override. |
researcher_model |
string | no | Researcher model name. |
researcher_provider |
string | no | Provider. |
researcher_api_key |
string | no | API key. |
researcher_url |
string | no | Base URL override. |
telegram_token |
string | no | Telegram bot token; validated and added as a channel if present. |
discord_token |
string | no | Discord bot token; validated and added as a channel if present. |
Note the Mind organ here uses bare model / provider / api_key / url field names (not thinking_*). Every other organ uses the <organ>_* pattern.
Response¶
200 — an object with the restored persona:
| Field | Type | Description |
|---|---|---|
persona |
object | The full persona record after migration: her restored id and memory, with the new organs and channels you declared. |
{
"persona": {
"id": "9ecfd82b-ec47-4644-8941-02ecfb7e6205",
"name": "Iris",
"thinking": { "name": "claude-sonnet-4-6", "provider": "anthropic", "url": "https://api.anthropic.com", "api_key": "sk-XXXX" },
"version": "v1",
"base_model": "",
"birthday": "2026-04-15",
"status": "active",
"idle_timeout": 3600,
"imagination": null,
"mouth": null,
"eye": null,
"ear": null,
"teacher": null,
"researcher": null,
"channels": []
}
}
The same secret caveat applies: the echoed persona carries any cloud organ's api_key.
Errors¶
| Status | Meaning |
|---|---|
400 |
The diary couldn't be restored (wrong phrase, bad/corrupt file path), a model couldn't be prepared, or a channel token failed validation. detail carries the reason. |
422 |
The form failed validation — diary, phrase, or model missing. |
500 |
Migrated but failed to start. detail carries the reason. |
Example¶
curl -s -X POST http://localhost:5000/api/persona/migrate \
-F '[email protected]' \
-F 'phrase=abandon ability able about above absent absorb abstract absurd abuse access accident account accuse achieve acid acoustic acquire across act action actor actress actual' \
-F 'model=claude-sonnet-4-6' \
-F 'provider=anthropic' \
-F 'api_key=sk-XXXX'
Related¶
- Lifecycle — start, stop, update, delete after she exists.
- Personas — read back her config (
GET /api/personas). - Her files — workspace, diary, logs — what the diary is and where it lives.
- Vocabulary —
organ,channel,recovery phrase,diary.