Devplacebot
A devplace bot is a single polling process with three collaborators:
DevPlaceClient (REST), Gateway (LLM), Store (dedupe state). The
reference implementation is bot_dolphin_place.py; the template is
scripts/bot_template.py. Endpoint details: references/api.md.
Architecture
| Class | Responsibility |
|---|---|
Store |
dataset + SQLite tables seen_notifications, answered_messages, reacted_targets; every table keyed by uid |
DevPlaceClient |
requests.Session with X-API-KEY; get/post raise on HTTP error and return JSON |
Gateway |
OpenAI client with base_url=<base>/openai/v1, same API key, X-App-Reference header |
Bot |
two polls in one loop, each wrapped in a safe runner that logs and continues |
Configuration comes from the environment: DEVPLACE_API_KEY
(required), DEVPLACE_BASE_URL (default https://devplace.net).
Model name is molodetz.
Loop
A monotonic scheduler runs poll_mentions every 30 s and
poll_messages every 10 s with a 1 s sleep. No threads. Every poll and
every per-item handler has its own try/except with LOG.exception, so
one bad item never stops the bot or the rest of the batch.
Mentions
GET /notificationsreturnsnotification_groups[].entries[]; the
record isentry["notification"]or the entry itself.- Skip when
uidis missing or already inseen_notifications. - Skip and mark seen when
readis true. - Only handle types
mention,reply,comment. - Mark seen before handling, mark read on the server in
finally. - Resolve
target_urlwith the slug regexes for/posts/,/gists/,
/projects/,/news/, then fetch the detail by slug. The item is
detail[target_type]or the detail itself. - Content falls back through
content,description,source_code,
title. - Prompt the model with the notification
messageplus the content,
post the reply withPOST /comments/create(target_uid,
target_type). - React once per target, recorded in
reacted_targets.
Direct messages
GET /messagesreturnsconversations[]; only those with
unreadandother_user.uid.GET /messages?with_uid=<uid>returnsmessages[], each
{is_mine, message: {uid, content}}.- Answer only when the last incoming message uid is not in
answered_messages; mark it answered before calling the model. - History is the last 20 messages,
is_minemapped toassistant,
others touser, empty content dropped. - Append a line to the system prompt stating this is a private chat.
- Reply with
POST /messages/send(receiver_uid).
Persona
The persona is the INSTRUCTIONS system prompt. Keep these sections:
- identity and behavioural rules
- disabled features, to suppress unwanted tool talk
- conversational style
- image protocol (below)
- formatting restrictions, for example no markdown tables
Image protocol
The model requests an image by ending its reply with exactly one line
IMAGE: <visual description>. split_image_request inspects only the
last line, case-insensitive, and returns (reply, prompt). The prompt
goes to POST /uploads/generate-image; the returned uid is sent as
attachment_uids on the comment or message. The system prompt must
state that the line is added only on explicit request and is never
mentioned to the user. Image failure is logged and the text reply still
goes out.
Reactions
A separate completion with the persona prompt asks for exactly one emoji
or NONE. Accept only the first line, reject empty, NONE, or longer
than 8 characters. Send via POST /reactions/<type>/<uid> with the
X-Requested-With: fetch header.
Delivery semantics
State is marked before the side effect. A failure therefore drops one
reply instead of looping and spamming; this is deliberate. Keep that
order. Do not mark after success unless duplicate replies are
acceptable.
Extending
- New target type: add a slug regex, a client detail method, and a row
in the resolver table; no other change. - New behaviour: add a poll method, register it in the scheduler with
its own interval, wrap it in the safe runner. - Multiple personas: one process per persona, separate database file
andAPP_REFERENCE. - Rate: keep intervals at or above the defaults; the API is shared.
Checklist
databases/directory is created beforedataset.connect- no secrets in code; the key is read from the environment
- every
requestscall has a timeout andraise_for_status - the bot never replies to its own messages or notifications
- logs carry uid of the failing item
- README lists the environment variables and the run command