Skip to content

Repository files navigation

max

A QQ group-chat agent, written in Haskell.

CI GHC Nix Postgres License

It talks to QQ through NapCatQQ — OneBot 11 over a reverse WebSocket — and calls any OpenAI-compatible or Anthropic-native endpoint for the replies. Group chats and one-on-one private chats both go through the same pipeline.

QQ ──▶ NapCat ──OneBot 11 / reverse-WS──▶ max ──▶ LLM (OpenAI-compatible · Anthropic)
                                           │
                                           ├──▶ Postgres · pgvector · blob store
                                           └──▶ Docker · per-group sandbox · browser

Features

  • Agent loop — multi-turn tool calling, with !kill to cancel, !feedback to steer a running turn, !btw to ask something else without disturbing it, and reaction-based status on the trigger message.
  • Persistence — every message in Postgres, images and videos content-addressed on disk, forwarded chats expanded inline. Pending media downloads survive a restart, and SIGTERM lets in-flight turns finish first.
  • Memory — per-group and per-user facts injected into the prompt, written by an episode extractor when the conversation goes quiet (whole-arc extraction behind a watermark), consolidated by a nightly dream pass.
  • Skills — progressive disclosure: a one-line index in the system prompt, full manuals fetched on demand with use_skill. Builtin manuals ship inside the binary (self-knowledge, sandbox, web, office — the docs double as the bot's self-image); DB rows add or shadow them at runtime via the admin API.
  • Multimodal — inline images, video and avatars on capable profiles; unrelated history images loaded on demand.
  • Tools — web search, code sandbox (per-group Docker workspace), files, message search, browser (camoufox over MCP), bilibili, pins, poke.
  • Multiple LLM profiles — OpenAI-compatible or Anthropic-native, switched at runtime with !model.
  • Commands!help, !model, !persona, !proactive, !memory, !clear, !pin, !btw, !feedback, !kill, !version, …
  • Admin panel — a loopback JSON API + baked-in web panel (sessions, skills, memories, permissions, tasks, usage, logs, full LLM call bodies).
  • Optional: proactive triggering (a cheap intent classifier can start a turn on unaddressed chatter) and vector search (pgvector-backed semantic search over messages and memories).

@bot pingpong, no LLM call. Full behaviour reference in docs/features.md.

Quick start

# 1. Enter the dev shell (--impure: devenv as a flake module)
direnv allow                     # or: nix develop --impure

# 2. Configure secrets
cp .env.example .env             # NAPCAT_QQ, MAX_ACCESS_TOKEN, MAX_LLM_API_KEY
cp max.yaml.example max.yaml     # persona, profiles, memory/embedding sections

# 3. Bring up Postgres (127.0.0.1:5433, db `max`, pgvector + pg_trgm)
devenv up

# 4. Bring up NapCat and log in
docker compose up -d napcat

Open http://localhost:6099 and scan the QR with the bot account's mobile QQ. Login state persists to ./.napcat/, and the reverse-WS target ws://host.docker.internal:8080/onebot is preconfigured. Private chat only works with the bot account's QQ friends — that's QQ's rule, not ours.

# 5. Build the tool images (optional, but most tools need them)
sandbox-image/build.sh           # code-execution sandbox
browser-image/build.sh           # browser (multimodal profiles only)

# 6. Run — migrations apply on boot, the embed worker backfills on its own
cabal run max

Then, in QQ:

@bot ping               → pong       (fast path, no LLM call)
@bot 你好                → LLM reply
@bot !help              → list of !cmd verbs
@bot !model list        → available LLM profiles
@bot !memory            → what the bot remembers here

Reply to a message and @bot … to hand the model the quoted context — including an expanded 转发聊天记录, attached-file ids, and (on multimodal profiles) the quoted message's images.

Configuration

Three layered sources, first-Just wins per field:

  1. CLI flagsmax --help lists everything.
  2. EnvironmentMAX_LLM_API_KEY, MAX_DB_URL, MAX_PERSONA, … Devenv sources .env on shell entry.
  3. YAML--config-file PATH, MAX_CONFIG, ./max.yaml, or $XDG_CONFIG_HOME/max/config.yaml.

The only required value is one LLM api_key. Feature sections (search, memory, embedding, intent, …) stay off when absent. See .env.example and max.yaml.example for the full schema.

Tests

cabal test max-test        # in-memory: pure logic
cabal test max-test-db     # DB integration; needs MAX_TEST_DB_URL

Docs

features.md full behaviour reference
architecture.md layout, data flow, durability, effect stack
development.md tests, versioning, debugging
prompt-flow.md the complete JSON of a real dispatch

License

MIT © Hank Hogan

Releases

Packages

Contributors

Languages