Skip to content

Personas, Bots, and rooms ​

A persona is an identity that keeps its memory across conversations and has a stable address you can come back to. Its profile controls tools, permissions, model and execution — the persona answers "who this is and what it remembers", the profile answers "what it can call and how it runs", and rebinding the profile leaves the identity intact. A bot is the persistent conversation behind a persona, reachable by a scheduled prompt, another persona or a room; a room lets several personas discuss a topic in separate member sessions.

Start with a persona ​

Create a persona on the Personas page, or place a card at $KIKI_HOME/personas/release-guide/persona.md ($KIKI_HOME defaults to ~/.kiki):

yaml
---
name: Release guide
title: Release coordinator
job: Check release readiness
profile: agent
greeting: What are we preparing to release?
memory:
  shared: [global, workspace]
---
You coordinate release preparation. Ask for missing evidence and distinguish
confirmed facts from open questions.

The directory name is the stable ID: lowercase letters, digits, and single hyphens between words. The Markdown body supplies the identity. Optional model_alias and thinking_effort pick a configured model and effort; they grant no permissions, and fields such as tools are rejected because permissions belong to the profile.

Choose the persona when creating a GUI session, or start it from the terminal:

sh
kiki --persona release-guide

Every persona has one daily conversation — the same entry the sidebar row, the switcher, the session header and the persona page all open — so "ask this persona" always means the same conversation. A persona can hold several at once; switch between them from its conversation list. In the terminal, /persona switch release-guide opens a new session and leaves the previous conversation alone. /persona list shows the catalog.

An explicit model selection beats the persona's model; otherwise Kiki uses the persona's model before the profile's or the default. An explicitly selected profile replaces the persona's profile without discarding the identity.

Each session freezes its persona snapshot, so editing a card does not change an ongoing conversation — start a new session or rebuild its context. The opening greeting is local until you reply to it; opening a conversation does not put it in the model's history.

Memory and character cards ​

Persona memory follows the persona across profiles and models. Persona-specific entries are isolated from other personas, and by default the persona can also read shared global and workspace memory — set memory.shared: [] to exclude those. The memory page offers persona and persona-workspace scopes; Memory covers the model, the review inbox and the undoable history. Deleting a persona removes its namespaces, and if that cleanup fails the deletion reports the error and keeps the card so you can retry.

The Personas page imports and exports Character Card V3 in JSON, PNG and CHARX. Preview an import before saving: lorebook entries can become persona memory and therefore influence later model requests. Unknown card extensions survive an export, but binary assets other than the avatar are not fully retained, so keep the original if it has them. The avatar picker accepts PNG, JPEG and WebP up to 20 MB and uploads a 256-pixel crop in a circle or square frame that survives reloads. Remove avatar restores the initials without deleting the persona or its memories. Direct API uploads are still limited to 2 MiB. Duplicate creates a new identity without copying conversation state or private memory, and archive hides a persona from ordinary selection without erasing it.

Saved persona files are capped at 1 MiB for persona.md and 256 KiB each for examples and extensions, measured in UTF-8 bytes. Oversized content fails before the card or its memory changes, so shorten it and try again. An older invalid or oversized card can still be replaced with PUT /api/personas/{id} or removed with DELETE /api/personas/{id}, which removes its memory first.

The persistent conversation ​

A persona is reachable whether or not you have typed to it. Its daily conversation is that address: the sidebar row, the switcher, the session header and the persona page all open the same one, and the persona page lists the others so you can move the daily entry to a different conversation. Opening it for the first time creates it.

That same conversation is what a scheduled prompt, a message from another persona and a room seat address reach — there is no second persona to set up. It works in its own directory, $KIKI_HOME/bots/<id> by default, or the one named by home_workspace. Related limits live in config.toml:

toml
[bot]
enabled = true
max_handoffs_per_hour = 30
room_budget = 12

A session's delivery is either reply or message. New persona sessions use message mode when the persona omits delivery; an explicit session choice takes priority, and an existing session keeps its saved choice. Sessions without a persona, and personas with explicit reply, answer directly. The persona editor saves the delivery mode it shows, including an explicit reply.

In the message view, a completed turn's last complete ordinary reply also appears when that turn has not called the native SendMessage tool. Calling it suppresses this fallback even if the send fails, is cancelled, or is still pending. When a call sends a messages batch, each message grows in its own row while streaming and keeps its own delivery result. Long replies and sent messages load the rest of their text automatically. Failed turns keep a failure indicator. Switch to the process view to read ordinary text that is not shown as a message, including unfinished replies. This display fallback does not send to a room, another persona, or an IM channel; those destinations still need an actual delivery.

When SendMessage confirms delivery to the user, its result tells the model to end the turn without another closing message if the request is complete and no work remains. Progress updates still allow required tools and pending work to continue. A native-executor turn that ends in ordinary text instead receives guidance in the next model request when the tool is available; it does not add a request to the completed turn or resend its answer.

The shipped agent profile exposes SendMessage in message mode and omits it in reply mode. A custom profile with an explicit tools allowlist must include it; delivery mode does not bypass profile permissions. The tool table is frozen for a turn, so a delivery change applies to the next one.

SendMessage addresses the user or another persona with to: "@Name"; use the persona ID when names collide. A handoff can wake a closed conversation and counts toward the hourly limit, though retrying the same delivery key does not consume another slot. Attachments are immutable copies from the session workspace, its additional directories or the persona's own area, not arbitrary filesystem paths. Set an attachment's kind to request image, audio, voice, or video delivery; omitting it preserves ordinary file delivery. A message can contain attachments without text:

json
{
  "attachments": [
    { "path": "output/voice.ogg", "kind": "voice", "mime_type": "audio/ogg" }
  ]
}

Connect conversation channels ​

A user-directed SendMessage uses the Kiki in-app channel, named internal, and the conversation's enabled IM bindings. The in-app channel stays on when an IM binding is added. HTTP clients can inspect the channels with rest.imSessions.channelViews(sessionId).

Each attachment must be supported by at least one enabled channel. Otherwise the send fails with media_unsupported; with no enabled destination, it fails with no_destination. If only some channels support an attachment, Kiki sends it to those channels and tells the others which media were skipped. The receipt's channels list identifies each destination as delivered, unsupported, failed, or unknown; a delivered channel can also list skipped types in unsupported. Do not automatically resend an unknown outcome: the platform may already have accepted it.

Voice is an explicit intent, not permission to fall back to a file. The in-app channel plays audio and voice in its audio player. Discord, Slack, and Matrix accept ordinary audio but do not support outgoing native voice; Discord and Slack upload audio as attachments, while Matrix uses m.audio. Matrix connections support only unencrypted rooms and refuse plaintext sends into encrypted rooms.

HTTP clients configure these connections through rest.imSessions.setupCatalog() and setup(...). The catalog supplies each platform's required fields and actual capabilities:

PlatformRequired fieldsSetup route
Discordbot_tokendiscord_install builds a bot invitation from application_id; enable Message Content Intent in the developer portal.
Slackbot_token, app_tokenslack_manifest creates a Socket Mode app; OAuth can supply the bot token, but an app-level xapp token is still required.
Matrixhomeserver, access_tokenmatrix_discover reads login methods; matrix_sso_begin / matrix_sso_complete or matrix_password can obtain and save a token.

Call setup({ action: "save", provider_kind, fields }) to save credentials without starting a listener. Then use the returned provider_instance_id with check and listen(id, true), send the bot a platform message, and confirm the resulting discovery against the intended session or persona. Existing Telegram, Feishu, OneBot, and WeChat connections keep their configuration and binding flow. The REST API guide covers connection authentication; the HTTP endpoints for these operations are GET / POST /api/im/sessions/setup and GET /api/im/sessions/targets/{sessionId}/channels.

After confirming a binding, call rest.imSessions.testBinding(bindingId) to send a fixed, clearly marked test message to that binding's chat without starting a model reply. It uses the existing connection and requires the binding to be enabled and connected. The HTTP equivalent is POST /api/im/sessions/bindings/{bindingId}/test with {}; custom text and targets are rejected. The result contains binding_id, status, message_ids, and an optional detail. A delivered result confirms platform acceptance, not that someone read the message; for failed, fix the reported cause before trying again. If the status is unknown, check the chat before manually retrying.

Discuss in a room ​

Create a room with two to six members, a classification workspace and a host. Persona members get their own message-mode sessions. The API also accepts existing threads (kind: "thread"), which reuse their own sessions, workspaces and permissions, need thread communication admitted for that thread, and cannot be subagents. Factory settings allow thread communication and leave it off. Turn that thread's session on, or turn the default on, before it can join. A user [thread_communication] enabled = false turns it off completely, and the thread's profile does not override a session that is off. In the GUI, Ctrl/⌘-click threads in the sidebar and choose Pull into a new room, use Add to room… on a thread, add them from the Threads tab under Add member, or pick Open a room with these threads on a thread link. Three rules decide who wakes:

  1. A user mention wakes the named members; @everyone selects all members.
  2. A user message without mentions goes to the host, whether the host is a persona or a thread.
  3. A persona or thread room message wakes only the members it mentions. A message with no mentions is logged but wakes no one.

Persona turns run one after another; existing threads each have their own queue. A muted member is skipped by agent mentions and host fallback, but an explicit user mention still wakes it. Each member sees messages since its last successfully completed catch-up, minus its own already-recorded output. Notifications already covered by that successful batch do not start separate turns; a failed or cancelled turn does not confirm the batch.

The budget limits member messages after each user message (12 by default). When it runs out the discussion pauses; Continue resets the budget and resumes the retained work. Pause cancels queued wakes but lets the active turn finish, and Stop all also interrupts an active persona turn — never an original thread task — without undoing completed actions. Persona-only rooms keep user interruption steering, mixed rooms keep queued work. One room question is shown at a time; later ones queue.

Thread members default to queueWhenBusy: true, so room input waits for their current turn instead of steering it, and cold threads resume in their own workspaces. Messages that do not select a member are included in that member's next catch-up without a separate model call. To speak, a thread must use ThreadSend({room, content, mentions?}); its ordinary assistant text never enters the room. Use exact member IDs (sessionId for a thread, personaId for a persona) in mentions, not display names. A send without mentions does not wake the host; that fallback applies only to user messages.

Renaming, changing the host, muting and workspace classification never rewrite a member's system prompt or permissions. Removing a thread leaves a system record in its session and preserves the room log without archiving the original thread. Creating a room needs two to six members, though members can leave and take it below two.

If a member cannot wake, the room shows the failure and what to do about it. For a model login failure, sign in under Settings → Models & providers → Providers & accounts, or open that member's conversation and pick an available model — existing member sessions keep their bound model, and editing the persona card does not change it. Then send another room message mentioning the failed member if it is not the host. Continue resumes a paused room; it does not retry a failed wake on an unpaused one.

API entry points ​

The SDK exposes global.personas, global.bots, and global.rooms; HTTP clients also have rest.personas, rest.bots, and rest.rooms. Lists return arrays directly. Session creation accepts persona and delivery; session reads return agent_config.persona = { id, name, avatarUrl? } and delivery.

REST resources are /api/personas, /api/bots, and /api/rooms. Room membership, mute state, host, and budget are updated with PATCH /api/rooms/{id}; pause, continue, and stop use their corresponding POST actions. GET /api/rooms/{id}/log accepts afterId and limit. Persona memory uses /api/memory/persona?persona_id=<id> or /api/memory/persona_workspace?persona_id=<id>&workspace_id=<workspace>.

Authentication and connection setup are described in the REST API guide.

Next steps ​