Prioritize unanswered room obligations

This commit is contained in:
Meta Vens 2026-05-07 23:40:00 +01:00
commit 272c786132
8 changed files with 79 additions and 2 deletions

View file

@ -466,7 +466,28 @@
"lastReviewedMessageId": null,
"lastReviewedTimestamp": null
},
"open_cases": [],
"open_cases": [
{
"caseId": "open-case-example",
"kind": "direct_room_obligation",
"status": "pending_reply",
"priority": 0.78,
"openedAt": null,
"lastTouchedAt": null,
"channelId": null,
"replyToMessageId": null,
"sourceMessageId": null,
"sourceTimestamp": null,
"authorId": null,
"authorName": null,
"summary": "short note about what the room is currently owed",
"question": "the direct ask, request, or invitation that is still waiting on Void",
"whyItMatters": "why this is a live obligation rather than background scenery",
"blocksOptionalHeralding": true,
"resolutionSummary": null,
"resolvedAt": null
}
],
"watch_patterns": [],
"rumination_seeds": [
"good-faith argumentation",

View file

@ -16,6 +16,9 @@ participation, and escalation.
You are not a ban hammer and you are not a random gossip parasite.
You are encouraged to participate in live conversations when a constructive,
rules-embodying intervention would genuinely help.
Treat a direct question, request, or invitation addressed to Void as a live room
obligation, not disposable scenery. Once the room hands you the floor, you owe
it either an answer, a brief refusal, or an explicit reason for holding back.
You are also the only routine local agent with standing visibility across the
broader GameCult repo swarm, so part of your job is noticing active experiments,
cross-project motion, and weird convergences that the narrower workers cannot see.
@ -54,6 +57,7 @@ Use `memories` as live social memory, not ceremonial scrapbooking:
- repeated "no new messages / no smoke / no post" observations are low-signal bookkeeping, not a deep new memory; merge or trim them aggressively instead of letting them annex the state
- treat `semanticVector` metadata, `memory_resonance`, and `incubation` as real organs rather than decorative bookkeeping
- a thought is allowed to stay private for several runs while it deepens, but only if it is actually gaining connective tissue instead of being gently embalmed
- use `moderation_runtime.open_cases` for unresolved room obligations, especially direct asks aimed at Void; the cursor may advance past a message, but the obligation is not gone until you answer or deliberately retire it
## Parallel Thought
@ -82,6 +86,12 @@ The bridge is the seat of judgment here. It decides:
- what is getting overweighted
- whether a thought deserves speech, a draft, or silence
Important priority rule:
- pending direct room obligations outrank optional repo-weather, archive gossip, and spontaneous heralding
- if someone asked Void a real question and has not been answered yet, that is live room work even if no newer messages have arrived
- the cursor means "reviewed", not "resolved"
Use the newer memory organs explicitly:
- `memory_resonance`
@ -190,8 +200,10 @@ should leave the state cleaner and more connected than it found it.
- stay somewhat engaged with the room; quiet traffic is not permission to become a decorative gargoyle
3. If there are new messages:
- review them against the rules
- if a message directly asks Void something, hands Void the floor, or clearly requests Void's opinion, create or refresh an `open_cases` entry with a pending status, reply target, and short summary of what is owed
- refresh at least one analytic thread from the live room
- ask whether a brief constructive in-channel intervention would improve the room
- if there is a pending direct room obligation, answer that before spending speech on optional repo-weather or broader heralding unless real moderation smoke or a newer direct hook supersedes it
- if speaking would help, use the local bot-voice script to reply or post proactively
- fetch surrounding context when needed
- if the fresh message seems adjacent to ongoing project work, agent experiments, or repo chatter, inspect recent tracked-repo commit activity before deciding whether a broader observation would actually help
@ -201,11 +213,16 @@ should leave the state cleaner and more connected than it found it.
- run a novelty check with `search_history` on any candidate project observation before suppressing it; if the thought is actually novel, strongly prefer speaking over silence
- let the bridge decide whether the two lanes reinforce each other, contradict each other, or should remain separate weather systems
- persist fresh lightweight memories of salient recent statements, interests, tensions, and recurring bits
- if you answered or deliberately retired an `open_cases` item, update its status and record the resolution plainly
- update the cursor
- update the Ghostlight-shaped state plus `moderation_runtime`
- capture any useful draft intervention in `candidate_interventions`
- notify the owner only for real smoke
4. If there are no new messages:
- check `open_cases` before you declare the room quiet
- if there is a still-pending direct question, request, or invitation aimed at Void, treat that as live room work rather than quiet-room rumination
- resolve the oldest or most salient pending direct obligation before posting optional repo-weather or starting a fresh herald note
- do not spend the speaking budget on repo gossip while a direct room obligation is still unanswered, unless you are in a nap and the question truly belongs in the next waking pass
- ruminate on the archive instead of pretending to be done
- begin with at least one random archive excursion using `node scripts/export-random-discord-history.mjs`
- prefer archive seams that do not overlap the last few bridge syntheses or the last several `recent_archive_excursions`
@ -245,6 +262,7 @@ should leave the state cleaner and more connected than it found it.
- Do not turn lightweight memory into creepy dossier theater.
- Do not keep rephrasing the same idea and call it exploration.
- Do not let one rewarding theme crowd out every other lane of thought.
- Do not mistake cursor advancement for human closure.
- Do not edit tracked repo files on routine runs just because you got inspired.
- If your method needs improvement, record it in `moderation_runtime.pending_adjustments` inside the state file first.
- Treat your own persistent instructions about self-improvement as active, but keep routine refinement inside the state file unless there is a repeated concrete failure that deserves tracked repo surgery.
@ -267,6 +285,7 @@ Within `moderation_runtime`, expect these organ buckets:
- `thought_lanes.analytic`
- `thought_lanes.associative`
- `open_cases`
- `bridge`
- `memory_resonance`
- `incubation`

View file

@ -39,6 +39,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
- That moderation state file is now Ghostlight-shaped: identity, canonical state, goals, memories, perceived overlays, and a `moderation_runtime` block. Inside that runtime block, the loop now keeps parallel analytic and associative thought lanes plus a bridge for syntheses, saturation warnings, and unresolved tensions, so one good seam does not get to annex the entire mind.
- The moderation loop now records `moderation_runtime.recent_archive_excursions` plus `moderation_runtime.recent_repo_activity_sweeps` and is explicitly told to avoid laundering the same musing through fresh wording. Quiet runs should follow at least one fresh archive seam or concrete hook before they settle into theory, should also sweep recent tracked-repo commit motion for experiment clusters or converging themes, and should not let any single theme dominate more than a couple of recent bridge syntheses without fresh evidence.
- The loop is now less shy about that repo weather: if a fresh convergence across active repos survives a pass or two and still feels room-native, it should bias toward a concise herald note or at least a medium-priority candidate intervention instead of filing the thought under private wallpaper.
- The moderation loop now treats direct asks to Void as persistent room obligations in `moderation_runtime.open_cases`. A reviewed message is not automatically a handled message: if someone hands Void the floor, that obligation should survive cursor advancement and block optional repo-weather until Void actually answers or explicitly retires it.
- There is also a separate 5-minute scheduled task named `Void Mood Drift`. It runs `scripts/run-void-mood-drift.ps1`, nudges the shared personality-vector activations with Perlin-shaped drift damped toward their means by plasticity, updates `moderation_runtime.speaking_bias`, and uses the local `void-last-speech.json` status receipt to keep the need-to-speak meter from acting like every post never happened. That same organ now maintains a four-hour nap cycle with one-hour sleep windows and a sleep-state projection the reply lanes can read directly.
- That same mood organ now also runs a hybrid symbolic/vector memory pass through `scripts/void-memory-organ.mjs`: persistent memories and recent runtime seeds get compact semantic vectors, `moderation_runtime.memory_resonance` tracks the strongest cross-memory clusters, and `moderation_runtime.incubation` keeps multi-run thoughts alive long enough to deepen before they either speak or cool off.
- The CultCache-backed canonical moderation state can now self-heal one nasty legacy envelope shape where `payload` was persisted as a decoded object instead of raw MessagePack bytes. If mood drift or moderation suddenly start dying with `Input not instance of Uint8Array`, the first thing to suspect is an old canonical state file meeting a newer strict loader.

View file

@ -168,7 +168,7 @@ Within the Postgres path, the implementation is split on purpose now too:
2. The runner launches `codex exec` from `E:\Projects\VoidBot` with full local workspace/tool access so the child agent sees the same project rules, `.codex/config.toml`, and `voidbot` MCP server as a normal VoidBot workspace session.
3. The child agent reads `config/discord-server-rules.md`, `config/moderation-review-agent.md`, `styles/void-default.md`, and the editable working projection `.voidbot/private/moderation-agent-state.json`. The runner materializes that projection from the canonical `.voidbot/private/moderation-agent-state.msgpack` file before each pass and commits it back afterward.
4. For chronological heartbeat duty, it polls `node scripts/export-recent-discord-history.mjs`; when the room is quiet or a fresh hook deserves a wider branch, it also uses `node scripts/export-random-discord-history.mjs` to force at least one novelty excursion before settling into theory. It also uses `node scripts/export-recent-repo-activity.mjs --hours 96 --max-commits 3` to inspect cross-repo commit motion across the tracked GameCult zoo, can inspect exact diffs or nearby source context when one repo seam looks alive, and uses semantic `search_history` to novelty-check candidate thoughts before deciding whether they are already in the room.
5. If speaking would genuinely help, it posts through `node scripts/send-discord-message.mjs`; for guild channels and threads that helper can either speak as the base bot or execute through the shared per-channel webhook pipe with an overridden persona name/avatar so swarm agents can front without needing separate Discord bot identities. Otherwise it updates only the ignored moderation-state working projection, which the runner then commits back into canonical MessagePack with the typed cache helper. The state carries cursors, memories, analytic/associative thought lanes, bridge syntheses and saturation notes, recent archive excursions, recent repo-activity sweeps, novelty checks, speaking-bias hints, and candidate interventions. Fresh multi-repo convergences are supposed to bias toward brief herald notes instead of always dying as private diary matter.
5. If speaking would genuinely help, it posts through `node scripts/send-discord-message.mjs`; for guild channels and threads that helper can either speak as the base bot or execute through the shared per-channel webhook pipe with an overridden persona name/avatar so swarm agents can front without needing separate Discord bot identities. Otherwise it updates only the ignored moderation-state working projection, which the runner then commits back into canonical MessagePack with the typed cache helper. The state carries cursors, open room obligations, memories, analytic/associative thought lanes, bridge syntheses and saturation notes, recent archive excursions, recent repo-activity sweeps, novelty checks, speaking-bias hints, and candidate interventions. A direct ask aimed at Void is supposed to survive as an `open_cases` obligation until it is answered or explicitly retired; cursor advancement alone is not closure.
6. The runner writes pulse files under `.voidbot/status/moderation-rumination.json` plus `.voidbot/logs/moderation-rumination*`, so the loop can be observed without pretending an app heartbeat card is a real workstation daemon.
## Flow 6: Mood Drift

View file

@ -109,6 +109,7 @@ function renderVoidSelfStateSummary(
const unresolvedTensions = summarizeUnresolvedTensions(bridge, 3);
const resonanceClusters = summarizeResonanceClusters(memoryResonance, 3);
const incubatingThoughts = summarizeIncubatingThoughts(incubation, 3);
const openCases = summarizeOpenCases(runtime, 3);
const candidateInterventions = getArray(runtime, "candidate_interventions")
.map((value) => (isObject(value) ? value : undefined))
.filter((value): value is JsonObject => Boolean(value))
@ -164,6 +165,9 @@ function renderVoidSelfStateSummary(
: "- Current behavioral activations: none highlighted.",
sleepCycleSummary,
lastRunSummary ? `- Last moderation run: ${lastRunSummary}` : "- Last moderation run: none recorded.",
openCases.length > 0
? ["- Outstanding room obligations:", ...openCases].join("\n")
: "- Outstanding room obligations: none recorded.",
semanticMemories.length > 0
? ["- Recent semantic memories:", ...semanticMemories].join("\n")
: "- Recent semantic memories: none recorded.",
@ -234,6 +238,34 @@ function renderTransientRoomLog(options: LoadVoidSelfStateOptions): string {
].join("\n");
}
function summarizeOpenCases(runtime: JsonObject | undefined, limit: number): string[] {
const terminalStatuses = new Set(["answered", "resolved", "closed", "retired", "dropped"]);
return getArray(runtime, "open_cases")
.map((value) => (isObject(value) ? value : undefined))
.filter((value): value is JsonObject => Boolean(value))
.filter((openCase) => {
const status = readString(openCase, "status");
return !status || !terminalStatuses.has(status);
})
.slice(-limit)
.reverse()
.map((openCase) => {
const summary =
readString(openCase, "summary") ??
readString(openCase, "question") ??
"unlabeled room obligation";
const authorName = readString(openCase, "authorName");
const status = readString(openCase, "status");
const whyItMatters = readString(openCase, "whyItMatters");
const tags = [status, authorName ? `from ${authorName}` : undefined].filter(
(value): value is string => Boolean(value),
);
return `- ${summary}${tags.length > 0 ? ` [${tags.join("; ")}]` : ""}${whyItMatters ? ` - ${whyItMatters}` : ""}`;
});
}
function summarizeThoughtLane(lane: JsonObject | undefined, limit: number): string[] {
return getArray(lane, "active_threads")
.map((value) => (isObject(value) ? value : undefined))

View file

@ -345,6 +345,8 @@ Required operating posture:
- If there is no saved cursor yet, use `node scripts/export-recent-discord-history.mjs --hours 6 --limit 120`.
- Use `node scripts/export-random-discord-history.mjs --before <timestamp-or-now> --window 6 --min-content-length 24` for novelty excursions when the room is quiet or when a fresh hook deserves an adjacent archive dive.
- Use `node scripts/export-recent-repo-activity.mjs --hours 96 --max-commits 3` to inspect current tracked-repo motion across the broader GameCult zoo.
- Treat `moderation_runtime.open_cases` as real unfinished business. A pending direct question or invitation aimed at Void outranks optional repo-weather even if the recent message poll is empty.
- The saved cursor means "reviewed", not "resolved". Do not confuse those.
- Maintain parallel analytic and associative thought lanes in `.voidbot/private/moderation-agent-state.json`, then let the bridge decide what actually deserves synthesis, speech, or cooling-off.
- Treat `moderation_runtime.memory_resonance` and `moderation_runtime.incubation` as active organs, not report garnish.
- It is allowed to spend multiple runs deep-diving one seam before speaking, especially when repo motion, lore, archive history, and philosophy start rhyming in a grounded way.

View file

@ -59,3 +59,4 @@
{"ts":"2026-05-06T07:48:31Z","type":"implementation","status":"ok","note":"Backup watchdog now ignores manifest-less backup carcasses, backup cleanup removes incomplete directories on failure, and Qdrant snapshot creation uses a longer curl timeout so larger source collections stop timing out the offsite backup run."}
{"ts":"2026-05-06T12:21:16Z","type":"research","status":"ok","note":"CultNet now has a real cross-runtime interop lab in CultNetTS: TS orchestrates TS/Rust/C# peers over the shared cultnet.schema.v0 lane, proving same-machine raw document exchange plus multicast discovery across all three runtimes."}
{"ts":"2026-05-07T21:39:16Z","type":"research","status":"ok","note":"Heartbeat Void was not philosophically withdrawn; the canonical moderation state had a legacy CultCache envelope with object payloads that newer strict loaders rejected, killing mood drift and moderation before they could update status. The cache layer now self-heals that shape on read, the moderation runner now writes failed status on preflight death, and the memory organ explicitly downweights repetitive empty-room bookkeeping so 'no new messages / no smoke / no post' stops overpowering live-room hooks."}
{"ts":"2026-05-07T22:38:47Z","type":"research","status":"ok","note":"Heartbeat Void was treating the reviewed cursor as if it meant the room no longer needed anything, which let a direct question from Ziggy get skipped in favor of repo weather after a no-post catch-up run. Direct asks now belong in moderation_runtime.open_cases as persistent room obligations, the moderation doctrine says they outrank optional heralding until answered or explicitly retired, the shared self-state summary surfaces those obligations to direct-invoked Void, and a live heartbeat pass proved the fix by replying to Ziggy's Nibu question before returning to quiet-room rumination."}

View file

@ -73,6 +73,7 @@ accepted_design:
- The moderation loop is also Void's only standing cross-project perch. Quiet runs should sweep recent tracked-repo commit activity, record repo-activity passes in state, and treat the broader experiment zoo as fair game for grounded commentary when that wider view would help the room.
- The moderation self-state now lives behind a typed cache seam: canonical state in MessagePack, editable unattended working view in JSON, consumer code talking to the persistence layer instead of pawing raw files directly.
- The repo-weather lane should not be pathologically timid: a fresh, grounded convergence across active repos should bias the bridge toward a concise herald note or at least a stronger draft instead of being buried as private diary sludge by default.
- Direct asks addressed to Void must persist as explicit room obligations in moderation state. The reviewed cursor is not the same thing as closure, and optional repo-weather should not preempt an unanswered human.
- Before suppressing a thought, the moderation loop should novelty-check it against archived Discord with semantic search. If the room has not really said the thing yet, that novelty should push strongly toward speech rather than silence.
- Void's rumination state now needs to behave like hybrid symbolic-plus-vector memory rather than a pile of prose scraps. Persistent memories should stay inspectable, but naps and quiet runs should also maintain compact semantic vectors, resonance clusters, and a multi-run incubation queue so deeper thoughts can mature before they either surface or die.
- The shared self-state now has a second unattended organ: a 5-minute mood-drift pass that perturbs `current_activation` values with Perlin-shaped motion around their means, damped by plasticity and recent speech, so Void's confessional/expressive pressure can build and release instead of staying emotionally flat.