Grillo — Chat Observer Beat
Part of the G.R.I.L.L.O. background subsystem.
Purpose
Passively watches recent conversations. It samples chat snippets, optionally stores passive memories from them, and — within strict anti-spam guards — can propose proactive outreach so Synth can gently re-engage a quiet conversation instead of only ever replying.
Beat
Trigger: periodic (its own interval,
GRILLO_OBSERVER_INTERVAL).Output: proposed chat snippets for processing and/or passive memories.
How it works
On each run the plugin looks at conversations active within
GRILLO_OBSERVER_ACTIVITY_WINDOW_DAYS and builds snippets from the human lines
in them (the synth’s own lines are never surfaced as reply targets). A human line
is offered as a reply target only while the conversation is still pending: a
chat that is live (some message younger than GRILLO_OUTREACH_QUIET_MINUTES)
and already answered (the synth’s own newest line is newer than the newest
line from anyone else) contributes its lines as context instead, tagged
you already answered this, not a reply target. Those lines are rendered into
the prompt but never enter grillo_snippets, so the routing guard cannot route a
reply into a conversation that is already up to date — the failure mode being a
beat “answering” a line the synth had just replied to and, in the reported case,
re-sending its own previous reply verbatim. An answered but idle chat still
offers its human lines: reaching out there later with something new is what the
run is for, and delivery is protected separately (below).
Proactive outreach is governed by two gates. A live conversation first: a
chat whose most recent message — from the human or from the synth — is younger
than GRILLO_OUTREACH_QUIET_MINUTES is marked LIVE-CONVERSATION and skipped
for that run. Second, and independently, the awaiting-reply gate
(GRILLO_OUTREACH_BLOCK_ON_SELF_LAST, on by default): when the synth’s own line
is the newest in a chat, the human has not answered yet, so the chat is marked
AWAITING-REPLY and held until they reply or
GRILLO_OUTREACH_SELF_LAST_WINDOW_MINUTES expires (12 h by default; 0 waits
indefinitely). Without it the hourly beat re-offered a thread it already
dominated and nagged it every run. Turn the gate off if it is measurably
silencing every outreach — the earlier removal of it was made on the grounds
that a responsive synth is the newest speaker in every chat and so the guard
matched every conversation, which is a property of an unbounded window rather
than of the gate itself.
Every conversation that clears both gates is offered to the model, and reaching
out to one of them is what the run is for: what has to be genuine is the
content (a new, grounded message — never a canned opener, never a restatement
of its own last message), not the fact of speaking. “I have nothing new to say
here” is a reason to write something else, not a reason to stay silent while a
target is offered. Speaking last is not itself a reason to stay silent — the
distinction the target list draws is between a thread marked AWAITING-REPLY
and one that is merely idle. Cadence belongs to GRILLO_OBSERVER_INTERVAL; the
last run is tracked in GRILLO_OBSERVER_LAST_RUN_TS. Discovery is automatic via
the plugin registry.
Independently of where a snippet came from, message_plugin refuses at delivery
any beat message whose text repeats the synth’s own recent line in that chat
(GRILLO_DUP_SIMILARITY_THRESHOLD), for every beat type and every chat type,
private DMs included. Outbound beats stay exempt from the public-chat gates —
reaching out is their purpose — but never from that repeat gate: a duplicate is a
duplicate wherever it lands.
Freshness is judged in wall-clock terms, not against the last-run cursor. A run that finds a message newer than its cursor but older than one full interval is treated as decay-driven, exactly as if there had been no message at all, so the synth is told the run exists to reach out instead of being left with only stale snippets it has been told not to answer. This is what keeps outreach happening after a restart: an outage longer than the interval would otherwise leave the cursor behind a message that is now hours old, and the run would go quiet.
Configuration
Key |
Purpose |
|---|---|
|
Enable/disable the observer. |
|
Seconds between observer runs (the outreach cadence). |
|
Whether to store passive memories from snippets. |
|
How recent a conversation must be to consider. |
|
Duplicate-suppression window for identical outbound messages. Does not gate outreach. |
|
Live-conversation guard: a chat with a message (either side) younger than this is skipped for that run. |
|
Awaiting-reply gate (default on): a chat whose newest message is the synth’s own is held until the human replies or the window below expires. Turn off to restore ungated outreach. |
|
How long that hold lasts, in minutes (default 720 = 12 h; |
Plus the shared Grillo settings. See the G.R.I.L.L.O. guide.