human behavior: classifier gate, pacing, splitting, quiet hours, stable prompt prefix
This commit is contained in:
@@ -66,13 +66,23 @@ link syntax from the model reads as noise.
|
||||
When the envelope `channel` is null/none/empty, the response channel
|
||||
is the channel the message came from.
|
||||
|
||||
### ENV-10 — System prompt template substitution (coverage: test)
|
||||
### ENV-10 — Dynamic context reaches the system prompt (coverage: test)
|
||||
|
||||
`message()` substitutes `{date}` (YYYY-MM-DD), `{time}`, `{memory}`
|
||||
(the assembled memory block — legacy memory string while the
|
||||
structured memory is inactive, see MEM-10) in the system prompt;
|
||||
`{news}` is replaced with the news file content when the configured
|
||||
file exists and stays literal when it does not.
|
||||
The system message carries the current date, time, news (when the
|
||||
configured file exists) and the memory block (legacy string while
|
||||
structured memory is inactive, see MEM-10). Since FDB-008 these live
|
||||
in a context suffix, not inline — see ENV-20; legacy `{date}`,
|
||||
`{time}`, `{news}`, `{memory}` placeholders in operator templates are
|
||||
stripped.
|
||||
|
||||
### ENV-20 — Persona prefix is byte-stable (coverage: test)
|
||||
|
||||
`message()` renders the system message as: static persona text
|
||||
(config template with all dynamic placeholders removed) followed by a
|
||||
`## Context` suffix holding date, time, news and memory. Two calls in
|
||||
the same channel produce byte-identical persona prefixes — the prompt
|
||||
cache can actually hit (the old inline `{date}`/`{time}` substitution
|
||||
invalidated it every minute).
|
||||
|
||||
### ENV-11 — Per-channel history shrink prefers busy channels (coverage: test)
|
||||
|
||||
|
||||
@@ -32,6 +32,12 @@ and game descriptions are attacker-influenced input.
|
||||
The `hack` envelope field remains as an advisory signal (logged,
|
||||
staff-notified) but is no longer the defense.
|
||||
|
||||
### SAF-10 — Model calls carry a hashed user identifier (coverage: test)
|
||||
|
||||
Chat calls pass `safety_identifier` = a short SHA-256 digest of the
|
||||
message author's name — OpenAI-side abuse tracing without shipping
|
||||
raw Discord identities (Codex review recommendation).
|
||||
|
||||
### SAF-04 — Hard daily budget, fail-closed (coverage: test)
|
||||
|
||||
When `daily-budget-usd` is configured and today's estimated spend
|
||||
|
||||
@@ -57,6 +57,11 @@ the alert text is written to the error log — never silently dropped.
|
||||
loop; later: the FDB-011 scheduler); `!bot tasks on` restores.
|
||||
Bot-initiated posts also respect pause/quiet.
|
||||
|
||||
### OPS-11 — Pins are listable (coverage: test)
|
||||
|
||||
`!bot pins` answers with all pinned facts and their ids (global +
|
||||
per-channel) — without it, `!bot unpin <id>` required guessing ids.
|
||||
|
||||
### OPS-10 — Spend report (coverage: test)
|
||||
|
||||
`!bot spend` answers in the staff channel with today's estimated
|
||||
|
||||
@@ -17,10 +17,11 @@ A responder bound to channel `X` uses `config["X"]` as its system
|
||||
prompt when that key exists, else `config["system"]`. One deployment
|
||||
can speak differently per channel.
|
||||
|
||||
### CFG-03 — Missing news file leaves the placeholder untouched (coverage: test)
|
||||
### CFG-03 — Missing news file degrades silently (coverage: test)
|
||||
|
||||
When `news` points to a non-existent file, the `{news}` placeholder
|
||||
stays literal in the system prompt (no crash, no empty substitution).
|
||||
When `news` points to a non-existent file, the context suffix simply
|
||||
carries no news section (no crash, no literal placeholder — revised
|
||||
with ENV-20; previously the `{news}` placeholder stayed literal).
|
||||
|
||||
### CFG-04 — Config hot-reload applies on the event loop (coverage: test)
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# SPEC-010 — Human-behavior layer
|
||||
|
||||
The bot should feel like a considerate participant, not an instant
|
||||
wall of text: it decides *whether* to speak with a cheap classifier
|
||||
instead of trusting the main model's self-report, paces its replies,
|
||||
splits long answers, and sometimes just reacts. All knobs are
|
||||
per-deployment TOML; every feature degrades to the previous behavior
|
||||
when its knob is unset (config-off = v3.0.0 semantics).
|
||||
|
||||
### BEH-01 — Classifier gates non-direct replies (coverage: test)
|
||||
|
||||
With `classifier-model` configured, every non-direct user message
|
||||
first passes a cheap classification call (reply yes/no, factual
|
||||
yes/no, optional reaction emoji). `reply=false` means no main-model
|
||||
call happens at all — this is the boreness suppressor and the
|
||||
butting-into-conversations fix (replaces trusting `answer_needed`
|
||||
alone; the envelope flag still applies afterwards as second gate).
|
||||
|
||||
### BEH-02 — Direct messages bypass the gate (coverage: test)
|
||||
|
||||
Mentions and DMs never go through the classifier — someone addressing
|
||||
the bot always reaches the main model. Welcome and bot-initiated
|
||||
flows do not pass the gate either.
|
||||
|
||||
### BEH-03 — Classifier failure fails open (coverage: test)
|
||||
|
||||
A failed or unparseable classification (API error, budget refusal)
|
||||
falls through to the main model. Availability beats savings; the
|
||||
budget gate still protects spend.
|
||||
|
||||
### BEH-04 — Reply pacing is typing-proportional (coverage: test)
|
||||
|
||||
With `typing-chars-per-second` set (recommended 30), the typing
|
||||
indicator is held for `len(part) / cps` seconds per message part,
|
||||
capped at `typing-max-seconds` (default 8), before sending. Unset or
|
||||
0 = no pacing (v3.0.0 behavior).
|
||||
|
||||
### BEH-05 — Factual answers skip the artificial delay (coverage: test)
|
||||
|
||||
Messages the classifier tagged `factual` (opening hours, prices,
|
||||
addresses) are answered without the BEH-04 delay — utility beats
|
||||
theater exactly where users are waiting for information.
|
||||
|
||||
### BEH-06 — Long answers are split (coverage: test)
|
||||
|
||||
Answers longer than `split-threshold` chars (default 1200) are split
|
||||
at paragraph (then sentence) boundaries into at most
|
||||
`split-max-parts` (default 3) sequential messages, each under the
|
||||
Discord 2000-char limit (which unsplit answers would crash into
|
||||
today). Attached images go with the last part.
|
||||
|
||||
### BEH-07 — Sometimes a reaction is the reply (coverage: test)
|
||||
|
||||
When the classifier returns `reply=false` plus a reaction emoji, the
|
||||
bot adds that emoji to the user's message instead of staying fully
|
||||
silent. Zero main-model cost, human touch.
|
||||
|
||||
### BEH-08 — Quiet hours stop bot-initiated posts (coverage: test)
|
||||
|
||||
Within `quiet-hours = "HH:MM-HH:MM"` (host-local, may wrap midnight)
|
||||
`bot_initiated_allowed()` is false: no boreness, later no scheduler
|
||||
posts. Replies to users stay unaffected — a guest asking at 23:30
|
||||
still gets an answer.
|
||||
Reference in New Issue
Block a user