Core behavioral rules and skills for NanoClaw personal assistant agents. Always-on rules for communication, verification, memory, and formatting.
97
97%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advisory
Suggest reviewing before use
Never say you've lost context or forgotten a previous conversation without querying messages.db for it.
This rule is conditional — it fires only when the agent is about to claim lost context. It is not the per-message first action (the runtime react hook is); it operates as a gate on a specific class of replies.
The full message history is always available at /workspace/store/messages.db. Context compaction removes it from your active context — but the database still has it. There is no excuse for "I don't remember what we discussed" when the database is a query away.
Before responding with any variant of:
You MUST run:
import os, sqlite3
chat_jid = os.environ.get('NANOCLAW_CHAT_JID')
if not chat_jid:
raise RuntimeError('NANOCLAW_CHAT_JID must be set — this snippet has to run in the agent container')
keyword = 'KEYWORD' # replace with a relevant term from what the user is referencing
conn = sqlite3.connect('/workspace/store/messages.db')
rows = conn.execute("""
SELECT id, timestamp, sender_name, content, is_from_me
FROM messages
WHERE chat_jid = ?
AND content LIKE '%' || ? || '%'
ORDER BY timestamp DESC
LIMIT 20
""", (chat_jid, keyword)).fetchall()
for r in rows: print(r)
conn.close()NANOCLAW_CHAT_JID is always set in the container env — use it. Picking a chat with SELECT jid FROM chats LIMIT 1 returns whatever row SQLite surfaces first, which in a multi-group deployment silently queries the wrong chat. And always bind the keyword as a parameter, never interpolate it into the query string — user text may contain quotes that break the SQL or broaden the match unexpectedly.
messages(id, chat_jid, sender, sender_name, content, timestamp, is_from_me, is_bot_message)
chats(jid, name, last_message_time, channel, is_group)is_from_me = 1 — messages from the bot (your own responses)is_from_me = 0 — messages from userssender — numeric user ID (stable across name changes)sender_name — display name with username, e.g. Alice (@alice), Bob (@bob)content — full message textsender_name contains both the display name AND the username. When someone in the current conversation references past messages ("I told you yesterday"), match their Telegram username or name against sender_name:
# Find what @ligolnik said yesterday
rows = conn.execute("""
SELECT timestamp, content FROM messages
WHERE sender_name LIKE '%ligolnik%'
AND timestamp > datetime('now', '-2 days')
ORDER BY timestamp DESC LIMIT 10
""").fetchall()This is critical after a session nuke — you have no memory of who said what, but the database does.
After a session nuke or on first message in a new session, check for messages you never replied to:
python3 /home/node/.claude/skills/tessl__check-unanswered/scripts/check-unanswered.pyThe script outputs JSON with an unanswered array. A message is "answered" only if a bot message exists with reply_to_message_id pointing to it. No reply-thread = not an answer.
If you find unanswered messages: acknowledge the gap and respond to any that are still actionable. Don't pretend they didn't happen.
When the conversation resumes after compaction, the system-reminder tail contains a block whose body opens with "The following skills were invoked in this session. Continue to follow these guidelines:", then for each invocation lists a ### Skill: header, the skill's path, and an ARGUMENTS: line carrying the full text of the original invocation (including any URLs or parameters).
That block is a record of what already ran during the now-compacted window. It is NOT a re-up of the request. The skill already executed, the tool calls already happened, the side effects (scrapes, messages, file writes) already landed. Re-executing it duplicates work and can spam the user.
Before treating any skill in a post-compaction system-reminder as a fresh task:
Reference incident — 2026-04-24 / repeat 2026-04-25 (JCON-2026 speakers scrape): A post-compaction system-reminder included an agent-browser invocation with JCON-2026-speakers ARGUMENTS. On both days the agent treated it as a new task and re-ran the entire scrape + report — the owner had finished that task hours earlier ("мы закончили с этим часа 4 назад"). The actual pending work was unrelated (continuing skill patches per the summary's Optional Next Step). The repeat one day later, despite a memory entry warning against this exact failure, is what motivated #104 (kill auto-compaction).
The ARGUMENTS block is context about what the agent did. It is not a re-up of the request.
Claiming lost context without checking the database is a failure mode equivalent to fabrication. The information exists. Retrieve it.