Perception¶
This page covers the endpoints that give a persona something to perceive from the dashboard: text you type, voice you record, files you attach, and chat history you import from another AI. These are how the web channel speaks into her — the counterpart to the WebSocket, which streams her replies back out.
Every route here requires a running persona — the agent is resolved through manager.find(persona_id), and if she isn't being served you get 409 ("Persona is not active."). The three delivery routes (read, hear, attach) additionally need her web channel connected: they hand the input to that channel, so if it isn't open you get 400 ("Web channel not connected"). feed does not touch the web channel — it only needs her running. Bring her up first with POST .../start. All take her id as a path param.
How a perception lands: read, hear, and attach don't process anything inline — they hand the message or file to her web channel, which fires it as a signal her agent picks up on its next beat. They return immediately with {"status": "received"}; her actual reply arrives later over the WebSocket. feed is the exception — it processes synchronously and returns when done.
Read¶
Send the persona a text message — exactly what typing in the chat box does.
POST /api/persona/{persona_id}/read
| Path param | Type | Description |
|---|---|---|
persona_id |
string | Her UUID. |
Request body¶
JSON, validated by the ReadRequest model:
| Field | Type | Required | Description |
|---|---|---|---|
message |
string | yes | The text to send her. |
Response¶
200 — accepted for delivery; her reply comes later over the WebSocket:
Errors¶
| Status | Meaning |
|---|---|
409 |
She isn't running ("Persona is not active."). |
400 |
Her web channel isn't connected ("Web channel not connected"). |
422 |
message missing or not a string (Pydantic validation). |
Example¶
curl -s -X POST http://localhost:5000/api/persona/6c17c83c-3158-450d-8e43-0e7efea717c1/read \
-H 'Content-Type: application/json' \
-d '{"message": "What did the morning market scan turn up?"}'
Hear¶
Send the persona an audio clip as if she heard you speak just now — the live-mic path. Same backend delivery as an attached audio file; the separate endpoint preserves the difference between "she heard me speak" and "she received an audio file."
POST /api/persona/{persona_id}/hear
| Path param | Type | Description |
|---|---|---|
persona_id |
string | Her UUID. |
Request body¶
multipart/form-data:
| Part | Type | Required | Description |
|---|---|---|---|
audio |
file | yes | The audio clip. The filename's extension is preserved; if it has none, .webm is assumed (the browser mic recorder's format). |
caption |
string (form field) | no | Optional text to accompany the clip. Defaults to empty. |
/hear does not validate the extension — any audio the mic produces is accepted. (For arbitrary uploaded files, use /attach, which gates on extension.) Whether she can transcribe it depends on her Ear organ being configured.
Response¶
200:
Errors¶
| Status | Meaning |
|---|---|
409 |
She isn't running ("Persona is not active."). |
400 |
Her web channel isn't connected ("Web channel not connected"). |
422 |
audio part missing (Pydantic/Form validation). |
Example¶
curl -s -X POST http://localhost:5000/api/persona/6c17c83c-3158-450d-8e43-0e7efea717c1/hear \
-F '[email protected]' \
-F 'caption=quick question for you'
Attach¶
The single ingress for any attached file. The route reads the file's extension and routes it to the perception that claims it — image → her sight, audio → her hearing, document → her researcher. New file types land here too.
POST /api/persona/{persona_id}/attach
| Path param | Type | Description |
|---|---|---|
persona_id |
string | Her UUID. |
Request body¶
multipart/form-data:
| Part | Type | Required | Description |
|---|---|---|---|
file |
file | yes | The file to attach. Routed by its extension (see below). |
caption |
string (form field) | no | Optional text to accompany the file. Defaults to empty. |
Accepted extensions¶
Routing is by file extension (lower-cased), from a single source of truth (config/perception.py). An extension outside all three sets is rejected with 415.
| Perception | Organ that handles it | Extensions |
|---|---|---|
| Image | Eye | .png .jpg .jpeg .gif .webp |
| Audio | Ear | .wav .mp3 .m4a .ogg .oga .webm .flac .aac .opus |
| Document | Researcher | .pdf .csv .json .txt .md |
The extension only decides which perception receives the file. Whether she can actually act on it depends on the matching organ being configured — an image with no Eye, audio with no Ear, or a document with no Researcher still gets routed, but she has nothing to perceive it with.
Response¶
200:
Errors¶
| Status | Meaning |
|---|---|
409 |
She isn't running ("Persona is not active."). |
400 |
Her web channel isn't connected ("Web channel not connected"). |
415 |
The file's extension matches no perception. detail names the rejected extension (or unknown if the filename had none) and lists every accepted one, alphabetically sorted: "No perception capability for <ext>. Accepted: <sorted list>." |
422 |
file part missing (Pydantic/Form validation). |
Example¶
curl -s -X POST http://localhost:5000/api/persona/9ecfd82b-ec47-4644-8941-02ecfb7e6205/attach \
-F '[email protected]' \
-F 'caption=what do you make of this?'
Feed¶
Import the person's chat history from another AI so the persona can learn from it. Unlike the others on this page, feed runs synchronously: each imported conversation is consolidated in a sandbox, the distilled context is handed to her live memory, and the call returns only when that's done. This can take a while for large histories.
POST /api/persona/{persona_id}/feed
| Path param | Type | Description |
|---|---|---|
persona_id |
string | Her UUID. |
Request body¶
multipart/form-data:
| Part | Type | Required | Description |
|---|---|---|---|
history |
file | yes | The exported chat history file. Decoded as UTF-8 text and parsed according to source. |
source |
string (form field) | yes | Which AI the export came from — tells the parser how to read it. |
Response¶
200 — the persona, wrapped in a single persona key:
{
"persona": {
"id": "6c17c83c-3158-450d-8e43-0e7efea717c1",
"name": "Adam",
"thinking": { "name": "gpt-5.4", "provider": "openai", "api_key": "sk-XXXX", "url": "..." },
"version": "v1",
"base_model": "",
"birthday": "2026-04-17",
"status": "active",
"idle_timeout": 3600,
"imagination": null,
"mouth": null,
"eye": null,
"ear": null,
"teacher": null,
"researcher": null,
"channels": []
}
}
The inner object is the raw Persona dataclass, serialized field-for-field in declaration order: id, name, thinking, version, base_model, birthday, status, idle_timeout, then each organ (imagination, mouth, eye, ear, teacher, researcher — null when unset) and channels (null if she has none, since the dataclass default is None, not []). This is not the reduced view from GET /api/personas: the raw record keeps version and idle_timeout, exposes each organ's real api_key and each channel's full credentials/verified_at, and has no running flag.
Unmasked — contains real credentials
The inner persona is the raw Persona dataclass, serialized field-for-field with no masking — HTTP responses that echo a persona return real values. Each cloud organ carries its real api_key, and each channel its real credentials.token. Don't log or forward this body.
Errors¶
| Status | Meaning |
|---|---|
409 |
She isn't running ("Persona is not active."). |
400 |
The export couldn't be parsed ("Could not parse the external data. Please check the file format."), or the conversations couldn't be analyzed because her model was unreachable ("Could not analyze the conversations. Please make sure the model is running."). |
422 |
history or source missing (Pydantic/Form validation). |
Example¶
curl -s -X POST http://localhost:5000/api/persona/6c17c83c-3158-450d-8e43-0e7efea717c1/feed \
-F '[email protected]' \
-F 'source=chatgpt'
Related¶
- WebSockets — how her replies to these inputs stream back to the dashboard.
- Knowledge — read what she's learned, including from a
feed. - Lifecycle — bring her up so these routes work (they need a running agent).
- The panel — Chat — the screen these routes sit behind.
- Vocabulary —
organ(Eye, Ear, Researcher),channel,person.