跪拜 Guibai
← All articles
Node.js

A WeChat Group Chat AI Built on GUI Automation: 5 Real Failures and 7 Anti-Robot Design Rules

By 假菌 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

When a platform refuses to let bots into a channel, GUI automation can impersonate a human at the OS level—but the cost is fragility, a full-time desktop, and a persistent account-ban risk. The hard problem isn't generating replies; it's deciding when not to speak, which inverts the usual priorities for chat-agent design.

Summary

Official WeChat channels leave no room for bots in ordinary group chats: personal-account AI is explicitly prohibited from being added to groups, Work WeChat bots are restricted to internal enterprise groups, and Tencent's own built-in AI can't send group messages. A working alternative operates a real WeChat account through GUI automation—locating the input box, writing to the clipboard, pasting, pressing Enter—and reads messages via local vision (Ollama) and speech-to-text (faster-whisper) models. In the group, its messages are indistinguishable from a human's in the moment they're seen.

A five-day run counted 229 group messages against 9 private ones, and the incident log records five real failures that shaped the system: duplicate sends from running two instances, replying to an 86-minute-old message, a 186-second queue delay, a good reply silently evicted by newer messages, and the worst—the model believing it had already said something it never actually sent. Each failure forced a design change, from a stale-message gate (10 minutes) to a queue that evicts the oldest rather than the newest, to separating "what to say" from "what was sent" in the state layer.

The system's guiding principle is that limiting the AI's ability to speak is ten times more important than making it good at speaking. It holds a 30-second minimum delay, uses truncated log-normal delays, sends at most three short lines in a burst, debounces incoming messages (1.5 seconds when @-ed, 6 seconds for normal chat), caps emoji at 8 per day with 15-minute intervals, carries only the last 20 messages as context, and builds per-member profiles with traits and evidence-backed facts. A separate journal counts deliberate silences (51 in the logged period) as its proudest metric. The whole thing runs on a secondary account in a small known group, and the risk of platform enforcement is stated plainly.

Takeaways
— Official WeChat channels prohibit bots in regular groups: personal-account AI is barred from groups, Work WeChat bots only enter internal enterprise groups, and Tencent's built-in AI cannot send group messages.
— A working alternative uses GUI automation: a real WeChat account, screen clicks, clipboard paste, and Enter key presses—no API, no protocol.
— Local vision (Ollama) and speech-to-text (faster-whisper) models handle images and voice, costing 4GB VRAM for vision and 1GB for audio transcription.
— Duplicate sends came from running two instances, not from a concurrency bug; a single-instance lock with process-liveness check fixed it.
— Replying to an 86-minute-old message is worse than silence; a stale-message gate of 10 minutes discards anything older.
— A 186-second queue delay showed that good replies expire while waiting; the queue now evicts the oldest message instead of dropping the newest.
— The most severe incident: the model believed it had already said something it never sent, because generation wrote to history before sending; separating "what to say" from "what was sent" is required, but remains unsolved.
— Reply delays use a truncated log-normal distribution with a 30-second lower bound to avoid the robot-feeling instant reply.
— Burst sending allows up to three short lines with pauses; merging consecutive messages from the same person uses a 1.5-second debounce when @-ed and 6 seconds for normal chat.
— Emoji are capped at 8 per day with 15-minute intervals, and never sent in private chat after one incident drew a "did you message the wrong person" response.
— Per-member profiles store traits and facts; facts must carry an original quote and timestamp, verified word-for-word against chat history to prevent hallucination.
— The system holds only the last 20 messages as context, up from 8, because 8 messages covered only a minute or two in an active group.
— A five-day log shows 229 group messages versus 9 private ones, a 25x difference, with most private replies triggered by the other person.
— The model chose silence 51 times deliberately during the logged period, which its creator counts as the most important metric.
— Output quota and timeout must be tuned together because a reasoning model spends the same quota on thinking and answering; raising to 16K required a longer timeout.
— Silence needed explicit logging—without it, debugging why the AI didn't respond took half an hour of elimination.
— The project runs on a secondary account in a ten-person known group, and stops immediately on any official warning.
— The setup is read-only on the client, using only screenshots, copy, paste, and Enter—no network protocol parsing, no process injection.
Conclusions

The platform's refusal to expose group chat bots is a design choice, not a capability gap, so any workaround carries an inherent ban risk and requires a clear ethical line.

The three-day build time was spent mostly on making the system shut up rather than talk, which inverts the typical feature-priority list for chat agents.

A model that believes it said something it never sent creates a social break that no prompt engineering can fix; the fix lives in the state layer, separating generation from delivery.

The 25:1 difference between group and private message volume shows that context availability, not model skill, determines whether an agent has anything to say.

Bypassing the bot label is the one part of the project its own creator admits to being unsure about; the justification rests on a small trusted group and immediate shutdown on warning.

Logging deliberate silences proved more valuable than logging speech, because the absence of a message is the hardest failure to debug without a trace.

Concepts & terms
GUI automation
Controlling a computer through simulated mouse and keyboard actions rather than APIs, here used to operate a WeChat client as if it were a human user.
Stale message gate
A threshold (10 minutes in this project) beyond which an incoming message is ignored, because replying to an old message looks more robotic than staying silent.
Truncated log-normal delay
A random delay distribution that models human response times accurately, with a hard lower bound (30 seconds) and a capped upper range, instead of a uniform random spread.
Anti-self-trigger
A filter that prevents the AI from responding to its own messages, which is necessary when the system reads the same chat stream it writes to.
From the discussion
Featured comments
afumu 1 likes

Incident 5, that 'it thought it had said it' — I stepped in the same pit on the other side: I thought it hadn't replied, but it actually had. On the web-end streaming response there's no reliable message ID, so the only way is to take a baseline snapshot of the reply list before sending and count the extra containers. But the outer container gets wholesale replaced before and after rendering, and the reference is already detached from the document, so what's read is still the previous round — in the group it looks like 'why is it repeating what it just said'. Switching to deduplication by text hash, when both rounds reply '好的' (okay) it gets falsely killed, and it keeps waiting until timeout. The judgement finally comes down to one criterion: the reply text is sampled twice in a row without changing, plus the send button returns to clickable. One different view: these two accounts shouldn't be charged to the conversation layer. Letting the model itself remember 'I said' is exactly the cause of incident 5. On my side, delivery success only writes an external marker, and I check it before retrying — keep it inside, and it will cover up lies for you.

假菌

A hug to a fellow professional.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗