A persona agent that knows how to talk — and when not to.
A single conversational character for group chats and DMs.
Situated in the room, selective by design, and able to learn without being rewritten by one noisy reaction.
Quick start · Why personagent · Deploy · Architecture · Docs
personagent turns an OpenAI-compatible chat model into a participant rather than an always-on assistant. AstrBot provides one transport layer for QQ, Telegram, Discord, Slack, Lark, KOOK, and other adapters; every platform shares the same persona, memory, safety, and learning pipeline.
You need Git, Python 3.10+, and one OpenAI-compatible API key. The local trial needs no messaging account or adapter.
git clone https://github.com/wangkant/personagent.git
cd personagent
python quickstart.pyThe wizard creates .venv, installs dependencies, writes .env, creates a persona, can check the model endpoint, and opens the terminal trial. It supports DeepSeek, Kimi, OpenAI, Ollama, and custom OpenAI-compatible endpoints.
# Open the trial again
.venv/bin/python try_chat.py # macOS / Linux
.venv\Scripts\python.exe try_chat.py # Windows
.venv/bin/python try_chat.py --lang zh # Chinese persona and data
.venv/bin/python try_chat.py --owner # speak as the configured ownerThe trial runs the production reply path: persona, retrieval, structured output, filters, and validator. Inside it, /owner <msg> speaks one line as the owner, /as <name> <msg> as another participant, /reset clears the buffer, and /quit exits.
| Situated persona Relationships, conversational position, intent, and scoped memory sit in the core reply path. |
Selective by designPASS is a first-class outcome. Silence can be the correct response. |
| Learning behind a gate Reactions become append-only evidence; only corroborated and promoted candidates affect future replies. |
Fail-closed delivery Structured output is parsed, filtered, policy-checked, and validated before anything is sent. |
Images, stickers, URLs, videos, and share cards can all become context. Chat, vision, evaluation, and judge calls remain vendor-neutral through OpenAI-compatible HTTP endpoints.
Live conversations use one route:
QQ / Telegram / Discord / Slack / Lark / KOOK / …
│
▼
AstrBot + forwarder
│
▼
personagent /webhook/gateway
- Run
python quickstart.pyand say yes to "connect to an AstrBot install": the wizard copies the bundled forwarder plugin into AstrBot'sdata/plugins/, generates the sharedGATEWAY_TOKENand writes it to both sides, writes the allowlists you give it, setsGATEWAY_NATIVE_PLATFORMS=aiocqhttpif QQ is included, and can switch on a platform in AstrBot's own config from a bot token (Telegram, Discord, Slack, KOOK, Lark). Non-interactive:python quickstart.py --astrbot <AstrBot data dir> [--qq] [--platform telegram --token <bot token>]. - Restart AstrBot (or reload plugins in its WebUI). Any other platform is configured in AstrBot itself.
- Start the agent with
python main.py(orstart.sh/start.ps1, which use the wizard's.venv).
Doing it by hand instead: copy the plugin folder into data/plugins/, set agent_url to http://127.0.0.1:8080/webhook/gateway, the same non-empty gateway_token as the agent's GATEWAY_TOKEN, and the allowlists; to include QQ, remove aiocqhttp from excluded_platforms and set:
GATEWAY_NATIVE_PLATFORMS=aiocqhttpThis keeps QQ identity, memory, and learning scopes stable while every platform enters through the same gateway. Keep the OneBot HTTP API (NAPCAT_API) reachable for QQ-specific background actions such as proactive sends and OCR. The plugin is default-deny and will forward nothing until its allowlists are configured. The older direct ingress, a OneBot client posting to /webhook/qq, is deprecated since 0.3.0: still served, but no longer the documented path.
For cross-host deployment, use HTTPS or a private tunnel and set the same non-empty GATEWAY_TOKEN in the plugin and agent. A public agent bind (HOST=0.0.0.0) also requires WEBHOOK_SECRET; startup refuses a non-loopback bind without both. See the deployment guide for the complete checklist, including how to verify each direction on its own.
Every message crosses the same boundary:
- Ingest — authenticate, limit, deduplicate, normalize, and enrich the event.
- Decide — resolve relationship and intent, retrieve scoped context, and decide whether to reply.
- Generate and validate — call the model through a structured contract, then filter and validate the result.
- Learn asynchronously — record reactions and evaluations as evidence without blocking the live reply.
Evidence alone changes nothing. Automatic promotion requires compatible corroboration, and every promoted candidate can be audited, rolled back, or superseded. Editing the persona document keeps everything learned; a new PERSONA_VERSION starts a clean slate. Mutable state stays under runtime/; the append-only ledgers remain the source of truth.
.env.example is the complete annotated reference. The setup wizard fills in the essentials; the startup preflight (also the first section of tools/healthcheck.py) reports missing required settings and any key the template does not list, so a typo is loud instead of silently ignored.
| Area | Settings |
|---|---|
| Model | LLM_API_KEY, LLM_BASE_URL, LLM_MODEL — any OpenAI-compatible /v1 endpoint. PRIVATE_MODEL, FALLBACK_MODEL, and JUDGE_MODEL share it. |
| Persona | BOT_NAME, AGENT_LANG, PERSONA_FILE, PERSONA_CARD_FILE — the card's reply_style opts into emoji and extra character sets. |
| Gateway | GATEWAY_TOKEN, GATEWAY_OWNER_IDS, GATEWAY_NATIVE_PLATFORMS; QQ-specific BOT_QQ, NAPCAT_API, OWNER_QQ. |
| Learning | PROMOTE_AUTO (conservative promotion, on); EVAL_ENABLE and EVOLVE_AUTO (self-evaluation and unattended diagnosis, off). |
| State | AGENT_HOME (deployment root; relative persona paths resolve under it), AGENT_RUNTIME_DIR (runtime/, gitignored). |
AGENT_LANG=en # English persona and data
# AGENT_LANG=zh # Chinese persona and datacurl http://127.0.0.1:8080/health # liveness; no model call
curl http://127.0.0.1:8080/health/details # dependency probes; needs X-Gateway-Token once GATEWAY_TOKEN is set
python tools/healthcheck.py # full diagnostic; model probes spend credits
python tools/candidates_admin.py list # what the bot has learned, and from which evidence
python -m pytest -q # offline regression suiteIn any chat the persona owns, @<BOT_NAME> what have you learned (or 你学到了什么) answers with that room's memories, the promoted replies and "not this, this" pairs in effect, proposals still waiting for a second voice, and the recent self-scores; what do you remember lists the memories. Neither calls the model.
Back up AGENT_RUNTIME_DIR together with private persona files, and never commit runtime state. It may contain credentials, account identifiers, conversation excerpts, reactions, and learned material. If the bot starts cleanly and never answers, work through when the bot goes quiet.
- Deployment guide
- AstrBot plugin
- Configuration reference
- Changelog
- Contributing
- Deployment disclaimer
Third-party protocol clients may violate platform terms or trigger account controls. Keep deployments private by default, protect secrets and conversation data, use a secondary account where appropriate, and obtain consent before processing other people's messages.
MIT © 2026 Qiankang Wang.
Built on the OneBot v11 event model, NapCat, AstrBot, FastAPI, and httpx, with ideas from Self-Feeding Chatbot, Alexa self-learning, and BlenderBot 3x. The lorebook and output-filter model follows SillyTavern's World Info and regex extensions.