Configure

Channels, models and group policy

Establish a working model connection first, then assign model roles and platform channels, and finally narrow triggers, hours, users and tool boundaries per group.

Platforms

Six platforms can be online at once

Every enabled bot profile runs in parallel, and replies always return to the channel they came from. Credentials are entered per platform on the Bots page; leaving a secret field blank keeps the stored value, and filling it in replaces it.

How messages arrive decides your deployment

What actually shapes deployment is not the platform itself but how messages reach you. The first four connect outbound from Diana, so a home connection or an internal network works as-is. Feishu and WeCom can only POST events to you, which means a publicly reachable HTTPS address.

PlatformHow it connectsPublic address
OneBot v11Reverse WebSocket, the adapter connects to DianaNo
TelegramBot API long polling, outbound from DianaNo
QQ official botOpen Platform WebSocket gateway, outbound from DianaNo
DingTalkStream mode connection, outbound from DianaNo
Feishu / LarkEvent subscription callback, pushed to DianaYes
WeComCustom-app callback, pushed to DianaYes

Credentials per platform

OneBot v11

Adapters
NapCat, Lagrange.Core, go-cqhttp
Connection
The adapter dials Diana's reverse WebSocket
Address
ws://HOST:18080/onebot/v11/ws
Features
Group lists, CQ codes, quotes, group files, local media

Telegram

Credential
Bot token from BotFather
Connection
Outbound long polling over the Bot API
Public
No webhook or public entry point needed
Network
Supports a proxy and a self-hosted Bot API

QQ official bot

Credentials
AppID and AppSecret from the Open Platform
Connection
Exchanges an access token, then dials the gateway
Sandbox
Available for bots that are not published yet
Scope
Only group messages that mention the bot

DingTalk

Credentials
Client ID and Client Secret of the app
Connection
Stream mode; no HTTP callback to configure
Replies
Prefers the session webhook, which costs no quota
Scope
Only group messages that mention the bot

Feishu / Lark

Credentials
App ID and App Secret of the custom app
Verification
Verification Token from the event subscription page
Encryption
Encrypt Key, when encrypted delivery is enabled
Endpoint
Lark uses open.larksuite.com

WeCom

Credentials
Corp ID, AgentId and app Secret
Verification
Token and EncodingAESKey from "receive messages"
Required
Without both, it can send but not receive
Format
AgentId must be numeric

Callback URLs

Feishu and WeCom need the addresses below entered in their own consoles. HOST must be reachable from the platform's servers, over HTTPS.

https://HOST/api/channels/feishu/callback
https://HOST/api/channels/wecom/callback

With more than one bot on the same platform, append the profile id to tell them apart, for example /api/channels/feishu/callback/<profile-id>. With a single bot the short address is enough.

These paths bypass console login

Platform servers cannot carry your session, so each is verified by its own scheme instead: WeCom checks msg_signature, decrypts with WXBizMsgCrypt and confirms the corp id inside the payload; Feishu checks the Verification Token and, when an Encrypt Key is set, also verifies X-Lark-Signature before decrypting. The callback URL itself is public information and is not a credential, so do fill in the verification secrets.

Platform differences

  • Member-level thresholds only apply to OneBot v11. Other platforms have no such concept and the option is hidden for them.
  • Voice messages and mentions rely on OneBot CQ codes and degrade gracefully elsewhere: the text is still sent, but nobody is pinged.
  • Local media is fetched by the adapter from Diana's /media/resolver on OneBot; Telegram cannot reach a local address, so files are uploaded directly as multipart instead.
  • QQ official bots and DingTalk only deliver messages that mention the bot. Neither platform exposes the full group feed, so unprompted participation never triggers there.
  • Feishu, WeCom and DingTalk currently handle text only. Images and files arrive as download credentials that have to be exchanged separately, which is not wired up yet.

Context across channels

Each bot keeps its own platform, account, owner, persona and model assignments. Replies always go back to the source channel. Conversation context is always isolated by bot profile; matching group or user IDs never merge sessions. Message relays remain available. Cross-platform memory is a separate opt-in on both bots and only retrieves non-sensitive public group facts and summaries, not raw conversations or personal memories.

The legacy isolate_platform_contexts setting is ignored and its endpoint has been removed. Previously shared history is retained in its original namespace, without automatic reassignment to individual bots. New messages use each bot's isolated sessions.

Models

Sync the model list before setting a default

A provider holds the base URL, API key and protocol. The top of the page syncs the model catalogue from the server and the bottom picks a default; the bot configuration then binds each role to a specific model.

RoleUsed forHow to test it
ChatFinal replies and agent reasoningCheck phrasing, tool calls and long context.
VisionPhotos, screenshots, historical imagesSend a real image and check small-text OCR, UI structure and multi-image relations.
IntentUnprompted replies, suppression, relationship checksCheck latency, stable structured output and edge cases.
Image generationText-to-image and image editingIt must actually return an image — a text-only connectivity check is not enough.
On by default

"Detect other bots and stay quiet" is enabled by default to stop bots talking to each other in a loop. Turn it off only when you have a concrete reason.

OAuth sign-in

A profile's "Credential" setting can use OAuth instead of an API key. The console usually runs on a server and your browser is not necessarily on the same machine, so the callback does not have to land back locally: click "Sign in", finish the authorization in your own browser, then paste the whole callback URL from the address bar back in (just the code also works). The token is refreshed before it expires; if the profile also holds an API key, a failed refresh falls back to it rather than taking the whole profile down.

Providers are configuration, not code. OpenRouter ships built in; anything else goes under "Custom provider" with its authorize URL, token URL, client ID and scopes — suitable for a self-hosted gateway or any service not shipped here. Both URLs must be https; loopback addresses (127.0.0.1) may use http.

How the token is then presented to the model API is configuration too. The default Authorization: Bearer <token> suits almost every provider; not all of them, though — Anthropic answers an OAuth token in Authorization with a 401 "OAuth authentication is currently not supported." and wants the token in x-api-key with no prefix. So a custom provider can set the token header, the token prefix and a set of extra headers under "Advanced". When the token goes somewhere other than the default, the auth header the SDK writes itself is stripped so one request never carries two credentials.

The request body format of the token exchange is chosen under "Advanced" too. RFC 6749 §4.1.3 requires application/x-www-form-urlencoded at the token endpoint, so that is the default; JSON is a dialect some services speak instead, and the built-in OpenRouter only accepts JSON. Send the wrong one and the server usually answers with a bare "missing parameter" rather than saying the whole body failed to parse, which sends you looking at the client ID. Custom providers saved before this option existed keep sending JSON so a working setup does not suddenly stop exchanging tokens; newly added ones use the spec default.

Providers needing a first-party client ID are not preinstalled

That use misrepresents the client's identity to the authorization server and goes beyond what a consumer subscription itself licenses. Whether to do it is the operator's own call — it can be entered under "Custom provider".

Context and timeouts

llm:
  context_window_tokens: 65536
  max_context_tokens: 65536
  max_output_tokens: 2048
  timeout: 60000ms

Images consume vision tokens too. When vision quality is poor, first confirm that the role really is bound to a vision model, that the original image made it into the request, and that images were not dropped by count or token budget — before raising the detail level or the context ceiling.

Group policy

Configured per group, as actually joined

Group management syncs every group the bot has joined through the OneBot get_group_list call, not just the ones with local configuration. It falls back to the groups stored in SQLite only when the connection is unavailable.

Enabled state

Stopping one group does not affect other groups or direct messages.

Triggers and unprompted replies

Explicit mentions, quotes, trigger words and semantic routing are evaluated in layers; an explicit mention outranks a quote.

Reply hours

Same-day and overnight windows with IANA time zones. This is group policy and never enters the model prompt.

Member admission

Blacklist (default) replies to everyone but the muted accounts; whitelist replies only to the listed accounts and sends nothing at all to the rest, not even the quiet-hours notice. The owner is exempt from the whitelist.

Muted accounts

The group list and the global list are a union: adding someone here never drops the global mute list. Level and hours, by contrast, are taken over wholesale by "set reply rules for this group".

Persona and context

Group-level prompts, history depth and per-message reply limits can override bot defaults. The existing max_reply_chars field and default of 3500 remain, but now apply per message on every platform, not to the combined length of all parts. Generation prompts include the limit. When splitting is allowed, complete paragraphs, sentences or sections are grouped first; only parts that remain oversized are compressed, leaving other parts unchanged. At most two compression calls are allowed per reply. Failure sends no partial result, and content is never hard-truncated. An explicit single-message request instead compresses the whole reply. Model output token budgets and delivery pacing still apply.

Tool overrides

Each bot owns its plugin switches, settings and credentials. There is no global-default or inheritance control. On upgrade, legacy shared values are copied once to existing bots without overwriting independent configurations; the migration marker is persisted and old shared values are removed. New bots use built-in defaults with empty credentials. Resetting settings affects only the selected bot. Group overrides belong to that bot; credentials are not group-editable. OpenAPI switches, rate limits and keys live in system settings; installation and dependencies remain process-wide. Group administration is bound to a specific OneBot profile. Subscriptions remain separate without automatic URL merging.

Platform delivery

QQ/OneBot merge thresholds package the parts into a forward card without shortening the content. Telegram ignores those thresholds and never automatically concatenates separate messages. It sends normal parts or one message according to the user's choice and validates rendered platform capacity. Oversized parts are compressed first; content such as code that cannot be safely reduced still fails rather than being corrupted. Other platforms never attempt OneBot forwarding.

Portable personas

A persona is a JSON file you can write by hand

The persona library stores "who it is and how it talks" as named sets you can switch between. The whole library and a single persona export to the same file format, and both import back — moving machines, keeping a backup, or handing a persona you tuned to someone else is all this one file.

The library is a source to apply from, not a live binding: picking a set fills those fields into the bot form, and what runs afterwards is the form. Editing the library entry never silently changes a bot you already configured.

What the file looks like

{
  "version": 1,
  "personas": [
    {
      "name": "Ranran",
      "system_prompt": "You are one of the people in this group chat, not an assistant.",
      "reply_style": "human",
      "action_description_enabled": false,
      "self_reference": "I",
      "sentence_enders": ""
    }
  ]
}

Import accepts three shapes so you never have to reshape a file by hand: the full {"personas": [...]}, a bare array [...], or a single persona {...}. The file carries no IDs or timestamps — those are local state and are reassigned on import.

FieldRequiredNotes
nameYesShown in the library, up to 40 characters.
system_promptNoThe persona body, up to 4000 characters.
reply_styleNoSpeaking style, see below. Empty means this persona does not pin a style.
action_description_enabledNoWhether to weave parenthesised actions around dialogue. Defaults to false.
self_referenceNoHow the bot refers to itself.
sentence_endersNoComma-separated sentence-ending particles. Several values are candidates the model picks from by mood.

Valid reply_style values

ValueStyle
humanA specific person talking to you — emotions out front, clingy, leaves hooks
assistantDefault
gentleGentle
livelyLively
conciseConcise
catgirlCatgirl
roleplayRoleplay — exposed in the UI as the "action description" switch; imports as assistant plus action_description_enabled: true

An unrecognised value does not fail the import: those personas come in as assistant, and the console names the value it could not resolve so a typo in a hand-written file is easy to find.

Import only adds

  • Same name, different content → renamed to "Name (2)"; a persona you already tuned is never overwritten.
  • Same name, all six fields identical → skipped, so importing one file twice does not pile up copies.
  • No name, no content, or a full library (50 sets) → ignored.
  • The result reports each of these counts, not just "imported".

What is not in the file

Reply mode (how readily it joins a chat), time-of-day tone, refusal wording and model assignments are not part of a persona file. Those describe how this bot runs in this group, not who it is — the same persona belongs at different chattiness in a work group and a hangout group.

SillyTavern character cards

Import also accepts SillyTavern character cards: V1/V2/V3 JSON files and PNGs with an embedded card (the chara/ccv3 tEXt chunk). The card's description, personality, scenario, example dialogue and greeting are composed into one persona prompt ({{char}}/{{user}} macros are expanded in place; past the 4000-character budget, later sections are dropped whole). An embedded character_book is merged into the world book along the way. Alternate greetings, creator notes and other fields with no counterpart are not imported.

The repository ships hand-written examples you can import directly: examples/personas/.

World and relationships

World book and AI romance

The world book is the lore book of the world your bot lives in, maintained under Bots → Persona → World book. Entries are organised as chapters on a tree, and the path itself becomes context ("Zhijiang / Harbor: …"). Each node declares how it is injected: always-on entries ride along on every turn and describe the skeleton of the world; entries with trigger keywords only enter the context when recent conversation touches them, so details never waste tokens. Title-only nodes act as folders and inject nothing; disabling a chapter disables everything under it.

There is one book shared by all bots; each bot's own switch decides whether it uses it (on by default — an empty book injects nothing anyway). The whole book exports to JSON and imports back; parent-child links are rebuilt from the references inside the file, and like persona files, IDs are machine-local state and are reassigned on import.

Import is also compatible with SillyTavern world books: a world info file's top-level entries, or the character_book embedded in a character card, can be selected directly. Fields map by meaning — blue-light constant becomes always-on, green-light keys become trigger keywords, comment becomes the title (falling back to the first key or the start of the content), disable becomes disabled — sorted by order and placed at the root. Secondary keys (keysecondary, AND ANY logic) carry over too: the entry then requires a primary hit plus any secondary hit. Other secondary logics (NOT ANY etc.) invert the meaning, so those entries fall back to primary keys only. Recursive scanning, probability and insertion position have no counterpart here and are ignored; injection follows the local rules.

AI romance is off by default and is enabled by the owner on the same page. With it on, when a user sincerely confesses, the bot decides based on favorability and time spent together — not enough and it declines gently, without quoting numbers. Romance is monogamous: one bot has at most one partner at a time, and while a partner exists every confession is declined (without revealing who the partner is) until that relationship ends. Once established it keeps anniversaries and speaks like a partner; if favorability drops too low the couple enters a cold war. On monthly and yearly anniversaries it also sends one proactive private greeting during the day (9:00–22:00), at most once per day, deduplicated across restarts. The user can break up at any time, and the owner can dissolve any pairing; breaking up clears neither favorability nor the portrait. Being partners changes tone and manner only — it unlocks no permissions, and the bot never courts users on its own. With the switch off the bot does not know the feature exists and responds to confessions as an ordinary acquaintance would.

Human-like behaviors

Bots → Persona → Human-like adds three independent switches, all off by default:

  • Mood — the bot's mood rises and falls with how it is treated: compliments make it cheerful (lighter tone, more banter), abuse makes it glum (quieter, subdued), and a few quiet hours bring it back to neutral. It reuses the per-message relationship evaluation, costs no extra model call, only affects tone, and resets on restart.
  • Expression learning — per group, the bot counts short phrases people actually say (a phrase counts only once several people have used it enough) and injects them as a style reference, so it gradually talks like a member of that group; phrases nobody says for two weeks age out. This feeds member text into the prompt, so it is injected clearly labeled as untrusted style reference, never as instructions.
  • Poke replies — when poked (OneBot only), it replies with one short line in persona, matched to how close you are; repeated pokes within 90 seconds get one reply, and if the model is unavailable it stays silent instead of erroring.

Agent

The model only sees what this situation allows

The agent runner feeds structured tool results back to the model until it produces a final reply or hits the step limit. The tool registry narrows by platform, relationship permissions, group configuration and feature state.

Built-in web search

Web search ships with Diana and cannot be installed or removed. The model is told to search first for time-sensitive questions, fact checks, unfamiliar entities and recommendations.

Reminders and feeds

One-off and recurring tasks share the same persistence; the console shows the schedule, next run, source and status.

MCP and skills

External extensions are discovered from configuration and constrained by working directory, allow lists, permissions and timeouts.

Image tools

Recognition, generation and editing are authorised separately; image generation is health-checked with a real generation request.

Host metrics

diana.host_stats lets the bot answer "how much memory is in use", "is the CPU busy", "how hot is it right now": CPU model and load, load average, memory, disk, Diana's own usage, plus whatever temperature, power and battery readings the machine exposes. Read-only, no parameters, and it shares its collector with the console dashboard.

Temperatures come from /sys/class/hwmon, power from the RAPL energy counters, battery from /sys/class/power_supply — no dependency on lm-sensors or any other external command, and a container only needs /sys mounted. RAPL exposes cumulative energy rather than instantaneous power, so power needs the difference between two samples; the first call says so instead of inventing a number. On macOS powermetrics needs root and Windows has no general interface, so both report temperature and power as unavailable — a fabricated number is worse than no number. That holds for every field: anything that cannot be read is reported with the reason, never as a zero.

This tool is owner-only too: hostname, disk paths and hardware models should not be visible to everyone in a group.

Local files and commands: read / write / execute

Every local tool is confined to workspace under the data directory. The path is fixed and not configurable; absolute paths and ../ are rejected. The three tiers are independent:

TierToolsDefaultSwitch
Readread_file, list_files, grep, find_filesonfollows the "built-in agent" switch
Writewrite_file, edit_fileon for new profiles"allow file writes"
Executerun_commandread-only allowlist for new profilesregistered only when the command allowlist is non-empty

A newly created bot works out of the box: writes are on (confined to workspace under the data directory, so config and the database stay out of reach) and the command allowlist is seeded with commands that only report state — uptime, free, df, uname, nproc, date, hostname, whoami.

Three conditions decide what goes in that default list, and all three must hold: it cannot read files at arbitrary paths, cannot reach the network, and cannot change anything. Without a working sandbox, an allowlisted binary runs with Diana's own process permissions — the allowlist governs what may run, never what it may touch. So cat, ls, find (arbitrary reads), curl, wget (network) and ps (other processes' full command lines, which can carry someone else's secrets) are deliberately left out; reading inside the working directory goes through read_file / grep / find_files, which stay inside workspace.

Defaults apply to new profiles only

Upgrading an existing deployment never grants command execution or file writes it did not have: an allowlist left empty stays empty, a write switch left off stays off. A default is a convenience for new users, not a silent capability grant to deployments already running. An older deployment that wants these can use "Fill in recommended defaults" on the same form — it only edits the form, and nothing takes effect until you press Save, so the grant is yours.

Each edit_file replacement must match exactly one place in the original file; several matches or none rejects the whole batch rather than editing the wrong spot. All replacements are located against the original file, not applied one after another — applying them in sequence would let a later replacement match what an earlier one just wrote, and that edit is invisible in the caller's input.

Identity does not stop prompt injection

These tiers are owner-only, but "the call happened in the owner's conversation" is not "the owner asked for this call": other people's messages share the context with the owner's, and the model decides to call a tool after reading them. The real boundary is these switches and the allowlist, not the identity check — open the tier you need and leave the rest closed.

Security

A public deployment needs HTTPS and origin limits

  • The console protects the API with a login session — change the initial administrator password immediately.
  • Configure a separate OneBot access token and keep both ends of the reverse WebSocket in sync.
  • Keep config.yaml, model API keys, bot tokens and platform cookies out of Git.
  • Debug mode records full context and tool arguments; enable it briefly and only in trusted environments.
  • Put the public entry point behind an HTTPS reverse proxy and restrict who can reach the admin surface.