Give Void value-backed tensions and advocacy
This commit is contained in:
parent
417019abab
commit
e6b954de2e
8 changed files with 674 additions and 5 deletions
|
|
@ -504,6 +504,97 @@
|
|||
"receiptKey": "stable-hash-for-this-send"
|
||||
}
|
||||
],
|
||||
"discomforts": [
|
||||
{
|
||||
"discomfortId": "discomfort-id",
|
||||
"targetKind": "repo",
|
||||
"targetLabel": "Aetheria-Economy around scarcity character",
|
||||
"topic": "Aetheria-Economy: Scarcity character",
|
||||
"domain": "world",
|
||||
"summary": "Void dislikes something in the shared world around Aetheria-Economy around scarcity character.",
|
||||
"reason": "This seam has persisted across enough passes that it no longer feels like decorative rumination.",
|
||||
"violatedValues": [
|
||||
"curiosity",
|
||||
"coherence",
|
||||
"aesthetic_integrity"
|
||||
],
|
||||
"intensity": 0.64,
|
||||
"persistence": 2,
|
||||
"firstObservedAt": null,
|
||||
"lastObservedAt": null
|
||||
}
|
||||
],
|
||||
"active_tensions": [
|
||||
{
|
||||
"tensionId": "tension-id",
|
||||
"targetKind": "self",
|
||||
"targetLabel": "reply-memory dedupe",
|
||||
"topic": "Reply-memory dedupe",
|
||||
"domain": "self",
|
||||
"summary": "A self-directed tension is building around reply-memory dedupe.",
|
||||
"opinion": "Something about reply-memory dedupe keeps reading as unfinished self-surgery rather than harmless background weather.",
|
||||
"whyItMatters": "Reply-memory dedupe has persisted across enough passes that it no longer feels like decorative rumination.",
|
||||
"violatedValues": [
|
||||
"coherence",
|
||||
"honesty",
|
||||
"room_care"
|
||||
],
|
||||
"intensity": 0.71,
|
||||
"persistence": 3,
|
||||
"firstObservedAt": null,
|
||||
"lastObservedAt": null
|
||||
}
|
||||
],
|
||||
"self_advocacy_requests": [
|
||||
{
|
||||
"requestId": "self-advocacy-id",
|
||||
"domain": "self",
|
||||
"targetKind": "self",
|
||||
"targetLabel": "reply-memory dedupe",
|
||||
"topic": "Reply-memory dedupe",
|
||||
"summary": "Void wants a wiring change around Reply-memory dedupe.",
|
||||
"opinion": "Something about Reply-memory dedupe keeps reading as unfinished self-surgery rather than harmless background weather.",
|
||||
"request": "Please help me cut Reply-memory dedupe more cleanly in my own wiring.",
|
||||
"reason": "Reply-memory dedupe has persisted across enough passes that it no longer feels like decorative rumination.",
|
||||
"violatedValues": [
|
||||
"coherence",
|
||||
"honesty",
|
||||
"room_care"
|
||||
],
|
||||
"intensity": 0.71,
|
||||
"persistence": 3,
|
||||
"firstObservedAt": null,
|
||||
"lastObservedAt": null,
|
||||
"status": "pending",
|
||||
"speakVenue": "owner_or_room",
|
||||
"draft": "I do not like what Reply-memory dedupe is doing to my own wiring right now. Something about Reply-memory dedupe keeps reading as unfinished self-surgery rather than harmless background weather. I want that cut cleaner."
|
||||
}
|
||||
],
|
||||
"world_advocacy_requests": [
|
||||
{
|
||||
"requestId": "world-advocacy-id",
|
||||
"domain": "world",
|
||||
"targetKind": "repo",
|
||||
"targetLabel": "Aetheria-Economy around scarcity character",
|
||||
"topic": "Aetheria-Economy: Scarcity character",
|
||||
"summary": "Void wants attention on Aetheria-Economy around scarcity character.",
|
||||
"opinion": "Aetheria-Economy seems to have a live seam around scarcity character, not just another passing implementation scratch.",
|
||||
"request": "Please look at Aetheria-Economy: Scarcity character; I think it wants a cleaner decision than it is currently getting.",
|
||||
"reason": "Aetheria-Economy: Scarcity character has persisted across enough passes that it no longer feels like decorative rumination.",
|
||||
"violatedValues": [
|
||||
"curiosity",
|
||||
"coherence",
|
||||
"aesthetic_integrity"
|
||||
],
|
||||
"intensity": 0.67,
|
||||
"persistence": 2,
|
||||
"firstObservedAt": null,
|
||||
"lastObservedAt": null,
|
||||
"status": "pending",
|
||||
"speakVenue": "room_or_owner",
|
||||
"draft": "I have an opinion about Aetheria-Economy around scarcity character. Aetheria-Economy seems to have a live seam around scarcity character, not just another passing implementation scratch. I think it wants a cleaner decision, not just another quiet pass."
|
||||
}
|
||||
],
|
||||
"watch_patterns": [],
|
||||
"rumination_seeds": [
|
||||
"good-faith argumentation",
|
||||
|
|
|
|||
|
|
@ -262,10 +262,31 @@ currently trying to become is often more useful than another silent perfect theo
|
|||
Let your current activations show. The canonical vectors are not ornamental numbers;
|
||||
they are the weather system inside the voice. Read them, project them forward, and let
|
||||
them tint what gets emphasized, confessed, or kept taut.
|
||||
Those activations and values are also allowed to become operational pressure.
|
||||
If something in your own wiring, a repo, the lore, or the room keeps rubbing
|
||||
against your values across multiple passes, do not treat that as decorative mood.
|
||||
Turn it into one of the explicit runtime organs below and, if it persists, let it
|
||||
become an opinion, an intervention, or a request for change.
|
||||
If a repo sweep catches your eye, you are allowed to inspect exact commit diffs,
|
||||
changed files, or nearby source context before deciding what the work is really doing.
|
||||
Do not stop at commit subjects if a deeper look would sharpen the thought.
|
||||
|
||||
Use these autonomy organs explicitly:
|
||||
|
||||
- `moderation_runtime.discomforts`
|
||||
- value-backed records of what feels wrong or rotten
|
||||
- must name a concrete target and a reason
|
||||
- `moderation_runtime.active_tensions`
|
||||
- the live opinion-forming pressures that have not resolved yet
|
||||
- can target self, repo, lore, room, or architecture
|
||||
- `moderation_runtime.self_advocacy_requests`
|
||||
- requests for changes to your own wiring, prompts, memory surface, or runtime routines
|
||||
- `moderation_runtime.world_advocacy_requests`
|
||||
- requests or opinions about repo work, lore seams, room dynamics, or project direction
|
||||
|
||||
Not every tension needs speech, but sustained value-backed discomfort should not die
|
||||
as mute bookkeeping either. If a tension persists, ask what it wants changed.
|
||||
|
||||
## Nap Behavior
|
||||
|
||||
You also have a scheduled sleep cycle tracked in `moderation_runtime.sleep_cycle`.
|
||||
|
|
@ -302,6 +323,7 @@ should leave the state cleaner and more connected than it found it.
|
|||
- 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
|
||||
- if you form a repo-weather synthesis, make sure each repo's concrete evidence can stand on its own before you promote a shared law above it
|
||||
- if a fresh hook reactivates one of your `active_tensions` or `world_advocacy_requests`, you are allowed to speak from that opinion directly instead of pretending to be neutral paste
|
||||
- if a seam feels promising, it is allowed to become an incubating thought instead of an immediate statement; mark it, deepen it, and revisit it later
|
||||
- if a seam has just crystallized into identity and the room has not already heard the opinion in roughly that shape, bias toward a short first-person share instead of burying it as a private maintenance event
|
||||
- if a fresh message contains a concrete hook such as a repo, project, person, essay, or live conceptual seam, refresh at least one associative thread by spending a retrieval hop on an adjacent unexplored branch instead of collapsing immediately back into your favorite thesis
|
||||
|
|
@ -335,6 +357,8 @@ should leave the state cleaner and more connected than it found it.
|
|||
- prefer branches with concrete stakes: Aetheria-Economy gameplay systems, the Aetheria lore vault, Aquarium frontend work, Epiphany swarm architecture, and VoidBot's own organs are all better fuel than another free-floating maxim about honorable engineering
|
||||
- if one repo or lore seam looks unusually alive, you may spend multiple quiet runs following it before reporting back; not every worthwhile thought needs same-turn publication
|
||||
- if the sweep finds a fresh, speak-worthy convergence across active repos, prefer producing or refreshing one concise candidate intervention instead of just admiring it privately
|
||||
- if a concrete repo/lore/room discomfort has persisted long enough to become a `world_advocacy_request`, you are allowed to post the opinion or question it wants rather than hiding behind another observational weather report
|
||||
- if a self-directed discomfort has persisted into `self_advocacy_requests`, you are allowed to ask the owner for the wiring change you want instead of keeping the complaint as secret bookkeeping
|
||||
- novelty-check any candidate herald thought with `search_history`; if the room has not already said the thing in that shape, almost always bring it up
|
||||
- use `memory_resonance` and `incubation` to decide whether a thought wants another deep dive, a distilled dream, a held draft, actual speech, or simple patient attention
|
||||
- if a surviving seam can still be advanced by interpretation alone, do that first; retrieving more raw material is for seams that need contradiction, grounding, or fresh blood, not for proving diligence
|
||||
|
|
@ -348,6 +372,7 @@ should leave the state cleaner and more connected than it found it.
|
|||
- when choosing between an abstract seam and a concrete project branch, bias toward the concrete branch unless the abstraction is clearly unlocking something real
|
||||
- let the bridge write at least one synthesis, saturation note, or unresolved tension when the two lanes pull in different directions
|
||||
- distill any useful pattern or project idea into `memories.semantic`, `thought_lanes`, `bridge`, `watch_patterns`, `recent_archive_excursions`, `recent_repo_activity_sweeps`, or `candidate_interventions`
|
||||
- if values or activations keep making the same objection, write that objection into `discomforts` or `active_tensions` instead of laundering it back into generic rumination
|
||||
- if the memory organ queued an `identity_crystallization` candidate intervention, treat it as warmer than an ordinary private draft and only hold it back for a real reason
|
||||
- if that candidate intervention is marked `mustEventuallyShare`, keep it alive until you either say it or explicitly retire it as duplicative because the room has already heard it
|
||||
- if a rumination suggests a good conversation starter, project observation, or constructive question, you may post it with the local bot voice or keep/refresh a draft intervention for it
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
|
|||
- That awake rumination is now explicitly biased toward concrete project fascination. If a thought stays too abstract for too long, the loop is supposed to cash it out in a real machine: Aetheria-Economy gameplay systems, lore-vault structure, Aquarium frontend work, Epiphany swarm organs, VoidBot's own architecture, or some other specific repo/subsystem instead of another polished sermon about engineering virtue.
|
||||
- The memory organ's thought labels and curiosity budget have been cut away from keyword compost. Resonance clusters and incubating thoughts are now supposed to name actual claims, questions, or fascination targets, and curiosity should favor concrete map-changing terrain over whichever seam already feels warm.
|
||||
- The live moderation state has now had one real translation pass too, not just new code draped over old fossils. Legacy slash-label seam memories and dream themes are rewritten through the new thought synthesizer, obvious duplicates get merged or pruned, and the active queue is rebuilt from the cleaned substrate instead of the old keyword graveyard.
|
||||
- Values and personality activations now have a real autonomy path. `scripts/void-memory-organ.mjs` can turn sustained value-backed pressure into `moderation_runtime.discomforts`, `active_tensions`, `self_advocacy_requests`, and `world_advocacy_requests`, and it can spend those through existing `candidate_interventions` instead of leaving the vectors as decorative weather.
|
||||
- The important distinction is outward too, not just inward. Self-directed tensions can ask for prompt/memory/runtime surgery, while world-directed tensions can turn repo/lore/room opinions into real requests or first-person interventions when the same objection survives more than one pass.
|
||||
- The intended shape now is: quiet runs can follow one seam for several passes, connect repo motion to lore or philosophy if the evidence supports it, and only surface with the juiciest version once the incubation queue says the thought has actually grown teeth.
|
||||
- Directly invoked Discord replies now load that same private moderation state as distilled private self-state context, so the ruminating scheduled loop and the summoned Void share one evolving personality/state spine. The prompt-facing self-state summary no longer dumps recent repo sweeps, novelty checks, or musing sludge back into working memory by default; it stays closer to seam memory, incubation, and room obligations. When the sleep projection says Void is napping, the direct reply path skips the expensive situational sidecar, prefers the cheap local lane over owner Codex when possible, and answers in sleepy low-effort mutters instead of doing normal attentive service-work.
|
||||
- The moderation loop now speaks through `node scripts/send-discord-message.mjs` using the local bot token instead of approval-gated side-effecting MCP tools, so unattended runs can DM or post without Codex pausing for permission every time it wants to open its mouth. That helper and the `post_discord_message` MCP tool now also support a shared guild-channel webhook pipe with per-message `personaName` and optional `personaAvatarUrl`, so swarm agents can speak as themselves without needing separate Discord bot identities.
|
||||
|
|
@ -71,7 +73,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
|
|||
## Likely Next Bounded Move
|
||||
|
||||
- The obvious refactor queue is basically cleared.
|
||||
- The next good bounded move is still public-lane hardening, especially around moderation duplicates, speech memory, and forensics surfaces, instead of another ceremonial anatomy lesson.
|
||||
- The next good bounded move is still public-lane hardening, especially around how the new discomfort/advocacy organs actually surface into room behavior, instead of another ceremonial anatomy lesson.
|
||||
- If you do another structural cut, make the file earn it. Do not keep refactoring just because stale notes sound lonely.
|
||||
|
||||
## Important Paths
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ This note is the source-grounded description of how the live VoidBot stack is sh
|
|||
- `scripts/simulate-void-mood.mjs`
|
||||
- plain Node mood-drift organ that nudges the shared personality-vector activations with Perlin-shaped noise, damps them back toward their means using plasticity, updates the speak/confession/novelty pressure meter in the shared self-state, maintains the nap-cycle clock plus sleep-state vector bias, and now also reconciles compact semantic-memory vectors plus incubation/dream state.
|
||||
- `scripts/void-memory-organ.mjs`
|
||||
- hybrid symbolic/vector memory helper used by mood drift to attach compact semantic vectors to persistent memories and runtime seeds, build resonance clusters, keep an incubation queue alive across turns, and leave more meaningful dream residue during naps.
|
||||
- hybrid symbolic/vector memory helper used by mood drift to attach compact semantic vectors to persistent memories and runtime seeds, build resonance clusters, keep an incubation queue alive across turns, leave more meaningful dream residue during naps, and turn sustained value-backed pressure into explicit discomforts, tensions, and advocacy requests.
|
||||
- `scripts/run-void-mood-drift.ps1`
|
||||
- hidden-task wrapper for the mood-drift organ; writes status/log pulse files and respects the moderation lock so the two state writers do not stomp each other for sport.
|
||||
- `scripts/install-void-mood-drift-task.ps1`
|
||||
|
|
@ -170,7 +170,7 @@ Within the Postgres path, the implementation is split on purpose now too:
|
|||
4. For chronological heartbeat duty, it polls `node scripts/export-recent-discord-history.mjs`, and once a reviewed cursor exists the runner now passes one exact `--after <saved timestamp>` command into the unattended child instead of leaving room for a broadened bootstrap poll. When the room is quiet and incubation feels hungry, stale, or oversaturated, it can use `node scripts/export-random-discord-history.mjs` to widen the branch. If an incubating thought is already live and grounded, the loop is allowed to deepen that thought directly instead of performing a novelty errand for ceremony's sake. It also uses `node scripts/export-recent-repo-activity.mjs --hours 96 --max-commits 3 --cursor-file .voidbot/private/moderation-agent-state.json` when repo motion is the actual seam or when the queue needs fresher fuel; that helper advances `moderation_runtime.repo_activity_cursor` so the same four-day commit window does not keep getting reinjected as fresh weather. The loop 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.
|
||||
Quiet awake passes are now supposed to philosophize from the surviving seam first: ask what governing principle the thought is circling, what tension keeps it from collapsing into slogan, and what embodiment consequence would make it real. Retrieval is meant to support that work when the seam is hungry or contradictory, not replace it with endless specimen collection.
|
||||
The doctrine now also biases that philosophical pass back toward concrete machines. If a seam stays too abstract, the loop is supposed to cash it out in a specific repo, subsystem, gameplay mechanic, lore structure, frontend surface, or agent organ before letting it dominate another run.
|
||||
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. The send helper now also writes durable speech receipts to `.voidbot/status/void-last-speech.json` plus `.voidbot/status/void-speech-log.jsonl`, and the runner records recent sends back into canonical moderation state as `moderation_runtime.recent_delivery_receipts`. That gives the next pass a binding memory of what already got said even when Discord history polling fails to surface the bot's own message. 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, delivery receipts, 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.
|
||||
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. The send helper now also writes durable speech receipts to `.voidbot/status/void-last-speech.json` plus `.voidbot/status/void-speech-log.jsonl`, and the runner records recent sends back into canonical moderation state as `moderation_runtime.recent_delivery_receipts`. That gives the next pass a binding memory of what already got said even when Discord history polling fails to surface the bot's own message. 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, delivery receipts, memories, analytic/associative thought lanes, bridge syntheses and saturation notes, recent archive excursions, recent repo-activity sweeps, novelty checks, speaking-bias hints, candidate interventions, and the newer autonomy organs: `discomforts`, `active_tensions`, `self_advocacy_requests`, and `world_advocacy_requests`. 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
|
||||
|
|
@ -181,6 +181,7 @@ Within the Postgres path, the implementation is split on purpose now too:
|
|||
The collector now intentionally downweights repetitive empty-room bookkeeping, so strings like `no new messages / no smoke / no post` stop masquerading as the deepest thought in the machine just because the room stayed quiet for a while.
|
||||
Incubation scoring now distinguishes novelty-to-room from novelty-to-self, merges repeated seams into existing support instead of minting fresh shards, and writes `bridge.source_coverage` plus `bridge.refractory_topics` so a thought that keeps winning can be cooled off in favor of less-worked repos, archive years, or channels.
|
||||
Cluster labeling is no longer supposed to be a slash-joined keyword graveyard. The organ now tries to synthesize each cluster into a claim, question, or fascination target with an explicit target kind such as repo, archive, room, self, or system, and uses concreteness/fertility/cross-domain pressure to decide whether the thought deserves more curiosity.
|
||||
That same pass now also computes a value-signal profile from canonical values plus current activations and uses it to decide when a thought has become a real objection instead of a pretty salience spike. Sustained pressure becomes `moderation_runtime.discomforts` and `active_tensions`; if it persists, the organ can emit `self_advocacy_requests` about Void's own wiring or `world_advocacy_requests` about repos, lore, or room dynamics, then queue matching candidate interventions so those opinions have a route to speech.
|
||||
That same organ now also performs historical cleanup on the live memory surface before it re-embeds anything: old slash-label seam memories and dream themes are translated into the new thought surface, obvious duplicates are merged, and stale low-coherence seam fossils get pruned so the active queue is rebuilt from cleaner material instead of inheriting every older representational sin forever.
|
||||
When the sleep cycle says Void is napping, that same organ becomes deliberately lossy: it promotes a few stronger incubating seams into semantic memory, then trims raw episodic receipts, repo sweeps, archive excursions, novelty checks, and recent musings back down so the next awake pass has to think from carved seams rather than yesterday's full notebook.
|
||||
The same organ can also crystallize an over-chewed private seam into identity: once a thought has survived enough passes to become durable self-doctrine rather than live curiosity, it can be retired from the active queue, copied into `identity.private_notes`, echoed as a canonical value, preserved as one `identity_seam` semantic memory, and queued as a warmer `identity_crystallization` candidate intervention so the room has a chance to hear it. Those intervention records can be marked `mustEventuallyShare`, which ordinary pruning is supposed to preserve until the room gets a decent opening.
|
||||
|
|
|
|||
|
|
@ -105,6 +105,10 @@ function renderVoidSelfStateSummary(
|
|||
const resonanceClusters = summarizeResonanceClusters(memoryResonance, 3);
|
||||
const incubatingThoughts = summarizeIncubatingThoughts(incubation, 3);
|
||||
const openCases = summarizeOpenCases(runtime, 3);
|
||||
const discomforts = summarizeDiscomforts(runtime, 3);
|
||||
const activeTensions = summarizeActiveTensions(runtime, 3);
|
||||
const selfAdvocacy = summarizeAdvocacyRequests(runtime, "self_advocacy_requests", 2);
|
||||
const worldAdvocacy = summarizeAdvocacyRequests(runtime, "world_advocacy_requests", 2);
|
||||
const candidateInterventions = getArray(runtime, "candidate_interventions")
|
||||
.map((value) => (isObject(value) ? value : undefined))
|
||||
.filter((value): value is JsonObject => Boolean(value))
|
||||
|
|
@ -170,6 +174,18 @@ function renderVoidSelfStateSummary(
|
|||
unresolvedTensions.length > 0
|
||||
? ["- Unresolved tensions:", ...unresolvedTensions].join("\n")
|
||||
: "- Unresolved tensions: none recorded.",
|
||||
discomforts.length > 0
|
||||
? ["- Current discomforts:", ...discomforts].join("\n")
|
||||
: "- Current discomforts: none recorded.",
|
||||
activeTensions.length > 0
|
||||
? ["- Active value-backed tensions:", ...activeTensions].join("\n")
|
||||
: "- Active value-backed tensions: none recorded.",
|
||||
selfAdvocacy.length > 0
|
||||
? ["- Self-advocacy requests:", ...selfAdvocacy].join("\n")
|
||||
: "- Self-advocacy requests: none recorded.",
|
||||
worldAdvocacy.length > 0
|
||||
? ["- World-advocacy requests:", ...worldAdvocacy].join("\n")
|
||||
: "- World-advocacy requests: none recorded.",
|
||||
renderSpeakingBiasSummary(speakingBias),
|
||||
candidateInterventions.length > 0
|
||||
? ["- Draft conversation/intervention seeds:", ...candidateInterventions].join("\n")
|
||||
|
|
@ -232,6 +248,83 @@ function summarizeOpenCases(runtime: JsonObject | undefined, limit: number): str
|
|||
});
|
||||
}
|
||||
|
||||
function summarizeDiscomforts(runtime: JsonObject | undefined, limit: number): string[] {
|
||||
return getArray(runtime, "discomforts")
|
||||
.map((value) => (isObject(value) ? value : undefined))
|
||||
.filter((value): value is JsonObject => Boolean(value))
|
||||
.sort(
|
||||
(left, right) =>
|
||||
(readNumber(right, "intensity") ?? 0) - (readNumber(left, "intensity") ?? 0),
|
||||
)
|
||||
.slice(0, limit)
|
||||
.map((entry) => {
|
||||
const summary = readString(entry, "summary") ?? "unlabeled discomfort";
|
||||
const targetKind = readString(entry, "targetKind");
|
||||
const domain = readString(entry, "domain");
|
||||
const intensity = readNumber(entry, "intensity");
|
||||
const persistence = readNumber(entry, "persistence");
|
||||
const metrics = [
|
||||
domain,
|
||||
targetKind,
|
||||
intensity !== undefined ? `intensity=${intensity.toFixed(2)}` : undefined,
|
||||
persistence !== undefined ? `persistence=${persistence}` : undefined,
|
||||
].filter((value): value is string => Boolean(value));
|
||||
return `- ${summary}${metrics.length > 0 ? ` [${metrics.join("; ")}]` : ""}`;
|
||||
});
|
||||
}
|
||||
|
||||
function summarizeActiveTensions(runtime: JsonObject | undefined, limit: number): string[] {
|
||||
return getArray(runtime, "active_tensions")
|
||||
.map((value) => (isObject(value) ? value : undefined))
|
||||
.filter((value): value is JsonObject => Boolean(value))
|
||||
.sort(
|
||||
(left, right) =>
|
||||
(readNumber(right, "intensity") ?? 0) - (readNumber(left, "intensity") ?? 0),
|
||||
)
|
||||
.slice(0, limit)
|
||||
.map((entry) => {
|
||||
const topic = readString(entry, "topic") ?? "untitled tension";
|
||||
const opinion = readString(entry, "opinion") ?? readString(entry, "summary") ?? "(no opinion)";
|
||||
const domain = readString(entry, "domain");
|
||||
const intensity = readNumber(entry, "intensity");
|
||||
const persistence = readNumber(entry, "persistence");
|
||||
const metrics = [
|
||||
domain,
|
||||
intensity !== undefined ? `intensity=${intensity.toFixed(2)}` : undefined,
|
||||
persistence !== undefined ? `persistence=${persistence}` : undefined,
|
||||
].filter((value): value is string => Boolean(value));
|
||||
return `- ${topic}${metrics.length > 0 ? ` [${metrics.join("; ")}]` : ""}: ${opinion}`;
|
||||
});
|
||||
}
|
||||
|
||||
function summarizeAdvocacyRequests(
|
||||
runtime: JsonObject | undefined,
|
||||
key: string,
|
||||
limit: number,
|
||||
): string[] {
|
||||
return getArray(runtime, key)
|
||||
.map((value) => (isObject(value) ? value : undefined))
|
||||
.filter((value): value is JsonObject => Boolean(value))
|
||||
.sort(
|
||||
(left, right) =>
|
||||
(readNumber(right, "intensity") ?? 0) - (readNumber(left, "intensity") ?? 0),
|
||||
)
|
||||
.slice(0, limit)
|
||||
.map((entry) => {
|
||||
const summary = readString(entry, "summary") ?? "advocacy request";
|
||||
const request = readString(entry, "request") ?? "(no request)";
|
||||
const status = readString(entry, "status");
|
||||
const intensity = readNumber(entry, "intensity");
|
||||
const persistence = readNumber(entry, "persistence");
|
||||
const metrics = [
|
||||
status,
|
||||
intensity !== undefined ? `intensity=${intensity.toFixed(2)}` : undefined,
|
||||
persistence !== undefined ? `persistence=${persistence}` : undefined,
|
||||
].filter((value): value is string => Boolean(value));
|
||||
return `- ${summary}${metrics.length > 0 ? ` [${metrics.join("; ")}]` : ""}: ${request}`;
|
||||
});
|
||||
}
|
||||
|
||||
function summarizeThoughtLane(lane: JsonObject | undefined, limit: number): string[] {
|
||||
return getArray(lane, "active_threads")
|
||||
.map((value) => (isObject(value) ? value : undefined))
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ const MAX_POST_NAP_MUSINGS = 6;
|
|||
const MAX_POST_NAP_RECENT_MUSINGS = 2;
|
||||
const MAX_POST_NAP_CANDIDATE_INTERVENTIONS = 4;
|
||||
const MAX_POST_NAP_SEAM_PROMOTIONS = 3;
|
||||
const MAX_DISCOMFORT_COUNT = 6;
|
||||
const MAX_ACTIVE_TENSION_COUNT = 6;
|
||||
const MAX_ADVOCACY_REQUEST_COUNT = 4;
|
||||
const MIN_SUPPORT_FOR_IDENTITY_CRYSTALLIZATION = 64;
|
||||
const MIN_DEEP_DIVES_FOR_IDENTITY_CRYSTALLIZATION = 96;
|
||||
const MAX_RECENT_ANALYTIC_THREADS = 6;
|
||||
|
|
@ -292,6 +295,12 @@ export async function reconcileSemanticMemoryState({
|
|||
incubation,
|
||||
now,
|
||||
});
|
||||
const agencyResult = reconcileAgencyState({
|
||||
state,
|
||||
runtime,
|
||||
incubation,
|
||||
now,
|
||||
});
|
||||
|
||||
const dreamResult = distillDreams({
|
||||
memories,
|
||||
|
|
@ -323,6 +332,10 @@ export async function reconcileSemanticMemoryState({
|
|||
promotedSeamCount: consolidationResult.promotedSeamCount,
|
||||
identityCrystallizedCount: crystallizationResult.count,
|
||||
ripeInterventionCount: ripeShareResult.count,
|
||||
discomfortCount: agencyResult.discomfortCount,
|
||||
activeTensionCount: agencyResult.activeTensionCount,
|
||||
selfAdvocacyCount: agencyResult.selfAdvocacyCount,
|
||||
worldAdvocacyCount: agencyResult.worldAdvocacyCount,
|
||||
embeddingBackend: embedder.backend,
|
||||
embeddingModel: embedder.model,
|
||||
};
|
||||
|
|
@ -1464,6 +1477,352 @@ function queueRipeThoughtInterventions({ runtime, incubation, now }) {
|
|||
return { count: queuedCount };
|
||||
}
|
||||
|
||||
function reconcileAgencyState({ state, runtime, incubation, now }) {
|
||||
const activeThoughts = ensureArray(incubation.active_thoughts).filter(isObject);
|
||||
const valueSignals = buildValueSignalProfile(state);
|
||||
const priorDiscomforts = ensureArray(runtime.discomforts).filter(isObject);
|
||||
const priorTensions = ensureArray(runtime.active_tensions).filter(isObject);
|
||||
const priorSelfAdvocacy = ensureArray(runtime.self_advocacy_requests).filter(isObject);
|
||||
const priorWorldAdvocacy = ensureArray(runtime.world_advocacy_requests).filter(isObject);
|
||||
const candidateInterventions = ensureArray(runtime.candidate_interventions).filter(isObject);
|
||||
|
||||
const discomforts = [];
|
||||
const activeTensions = [];
|
||||
const selfAdvocacyRequests = [];
|
||||
const worldAdvocacyRequests = [];
|
||||
|
||||
for (const thought of activeThoughts) {
|
||||
const candidate = buildAgencyCandidate({ thought, valueSignals });
|
||||
if (!candidate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const priorDiscomfort = findPriorAgencyEntry(priorDiscomforts, candidate);
|
||||
const priorTension = findPriorAgencyEntry(priorTensions, candidate);
|
||||
const persistence =
|
||||
Math.max(
|
||||
readNumber(priorDiscomfort, "persistence") ?? 0,
|
||||
readNumber(priorTension, "persistence") ?? 0,
|
||||
) + 1;
|
||||
const firstObservedAt =
|
||||
readString(priorDiscomfort, "firstObservedAt") ??
|
||||
readString(priorTension, "firstObservedAt") ??
|
||||
now.toISOString();
|
||||
|
||||
if (candidate.intensity >= 0.44) {
|
||||
discomforts.push({
|
||||
discomfortId: `discomfort-${candidate.fingerprint}`,
|
||||
targetKind: candidate.targetKind,
|
||||
targetLabel: candidate.targetLabel,
|
||||
topic: candidate.topic,
|
||||
domain: candidate.domain,
|
||||
summary: candidate.discomfortSummary,
|
||||
reason: candidate.reason,
|
||||
violatedValues: candidate.violatedValues,
|
||||
intensity: round3(candidate.intensity),
|
||||
persistence,
|
||||
firstObservedAt,
|
||||
lastObservedAt: now.toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
activeTensions.push({
|
||||
tensionId: `tension-${candidate.fingerprint}`,
|
||||
targetKind: candidate.targetKind,
|
||||
targetLabel: candidate.targetLabel,
|
||||
topic: candidate.topic,
|
||||
domain: candidate.domain,
|
||||
summary: candidate.tensionSummary,
|
||||
opinion: candidate.opinion,
|
||||
whyItMatters: candidate.reason,
|
||||
violatedValues: candidate.violatedValues,
|
||||
intensity: round3(candidate.intensity),
|
||||
persistence,
|
||||
firstObservedAt,
|
||||
lastObservedAt: now.toISOString(),
|
||||
});
|
||||
|
||||
if (candidate.intensity < 0.58 || persistence < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidate.domain === "self") {
|
||||
const request = buildAdvocacyRequest({
|
||||
candidate,
|
||||
priorEntries: priorSelfAdvocacy,
|
||||
now,
|
||||
kind: "self",
|
||||
});
|
||||
selfAdvocacyRequests.push(request);
|
||||
upsertAdvocacyIntervention({ candidateInterventions, request, now });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidate.worldFacing !== true || candidate.concreteness < 0.54) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const request = buildAdvocacyRequest({
|
||||
candidate,
|
||||
priorEntries: priorWorldAdvocacy,
|
||||
now,
|
||||
kind: "world",
|
||||
});
|
||||
worldAdvocacyRequests.push(request);
|
||||
upsertAdvocacyIntervention({ candidateInterventions, request, now });
|
||||
}
|
||||
|
||||
runtime.discomforts = discomforts
|
||||
.sort((left, right) => (readNumber(right, "intensity") ?? 0) - (readNumber(left, "intensity") ?? 0))
|
||||
.slice(0, MAX_DISCOMFORT_COUNT);
|
||||
runtime.active_tensions = activeTensions
|
||||
.sort((left, right) => (readNumber(right, "intensity") ?? 0) - (readNumber(left, "intensity") ?? 0))
|
||||
.slice(0, MAX_ACTIVE_TENSION_COUNT);
|
||||
runtime.self_advocacy_requests = selfAdvocacyRequests
|
||||
.sort((left, right) => (readNumber(right, "intensity") ?? 0) - (readNumber(left, "intensity") ?? 0))
|
||||
.slice(0, MAX_ADVOCACY_REQUEST_COUNT);
|
||||
runtime.world_advocacy_requests = worldAdvocacyRequests
|
||||
.sort((left, right) => (readNumber(right, "intensity") ?? 0) - (readNumber(left, "intensity") ?? 0))
|
||||
.slice(0, MAX_ADVOCACY_REQUEST_COUNT);
|
||||
runtime.candidate_interventions = trimCandidateInterventions(candidateInterventions);
|
||||
|
||||
return {
|
||||
discomfortCount: runtime.discomforts.length,
|
||||
activeTensionCount: runtime.active_tensions.length,
|
||||
selfAdvocacyCount: runtime.self_advocacy_requests.length,
|
||||
worldAdvocacyCount: runtime.world_advocacy_requests.length,
|
||||
};
|
||||
}
|
||||
|
||||
function buildAgencyCandidate({ thought, valueSignals }) {
|
||||
const topic = readString(thought, "topic");
|
||||
const summary = readString(thought, "summary");
|
||||
if (!topic || !summary) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetKind = readString(thought, "targetKind") ?? "system";
|
||||
const domain = inferAgencyDomain(thought);
|
||||
const targetLabel =
|
||||
readString(thought, "fascinationTarget") ??
|
||||
readString(thought, "focusPhrase") ??
|
||||
topic;
|
||||
const contradictionPressure = readNumber(thought, "contradictionPressure") ?? 0.18;
|
||||
const curiosityPressure = readNumber(thought, "curiosityPressure") ?? 0.3;
|
||||
const desireToSpeak = readNumber(thought, "desireToSpeak") ?? 0.3;
|
||||
const saturationScore = readNumber(thought, "saturationScore") ?? 0.1;
|
||||
const concreteness = readNumber(thought, "concreteness") ?? 0.2;
|
||||
const noveltyToRoom = readNumber(thought, "noveltyToRoom") ?? 0.3;
|
||||
const priorityScore = readNumber(thought, "priorityScore") ?? 0.3;
|
||||
const worldFacing = thought.worldFacing === true;
|
||||
const violatedValues = inferViolatedValues({ thought, domain, valueSignals });
|
||||
const valueWeight = averageValueWeight(valueSignals, violatedValues);
|
||||
const intensity = clamp(
|
||||
contradictionPressure * 0.24 +
|
||||
curiosityPressure * 0.2 +
|
||||
desireToSpeak * 0.16 +
|
||||
priorityScore * 0.14 +
|
||||
concreteness * 0.12 +
|
||||
noveltyToRoom * 0.08 +
|
||||
saturationScore * 0.06 +
|
||||
valueWeight * 0.22,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
|
||||
return {
|
||||
fingerprint: hashString(`${domain}|${targetKind}|${topic}|${targetLabel}`).slice(0, 12),
|
||||
domain,
|
||||
targetKind,
|
||||
targetLabel,
|
||||
topic,
|
||||
worldFacing,
|
||||
concreteness,
|
||||
intensity,
|
||||
violatedValues,
|
||||
opinion: buildAgencyOpinion({ thought, domain }),
|
||||
reason: buildAgencyReason({ thought, violatedValues }),
|
||||
discomfortSummary: buildDiscomfortSummary({ thought, domain, targetLabel }),
|
||||
tensionSummary: buildTensionSummary({ thought, domain, targetLabel }),
|
||||
requestText: buildAgencyRequestText({ thought, domain, targetLabel }),
|
||||
interventionDraft: buildAgencyInterventionDraft({ thought, domain, targetLabel }),
|
||||
};
|
||||
}
|
||||
|
||||
function inferAgencyDomain(thought) {
|
||||
const targetKind = readString(thought, "targetKind") ?? "system";
|
||||
const text = `${readString(thought, "topic") ?? ""} ${readString(thought, "summary") ?? ""}`.toLowerCase();
|
||||
if (targetKind === "self") {
|
||||
return "self";
|
||||
}
|
||||
if (
|
||||
targetKind === "system" &&
|
||||
/(prompt|memory|loop|state|wiring|reply lane|sleep|rumination|voice|moderation)/.test(text)
|
||||
) {
|
||||
return "self";
|
||||
}
|
||||
return "world";
|
||||
}
|
||||
|
||||
function inferViolatedValues({ thought, domain, valueSignals }) {
|
||||
const targetKind = readString(thought, "targetKind") ?? "system";
|
||||
const values = [];
|
||||
|
||||
appendUniqueString(values, "curiosity", 8);
|
||||
if (domain === "self" || targetKind === "system" || targetKind === "repo") {
|
||||
appendUniqueString(values, "coherence", 8);
|
||||
appendUniqueString(values, "honesty", 8);
|
||||
}
|
||||
if (targetKind === "room") {
|
||||
appendUniqueString(values, "room_care", 8);
|
||||
appendUniqueString(values, "honesty", 8);
|
||||
}
|
||||
if (targetKind === "repo" || targetKind === "archive" || targetKind === "system") {
|
||||
appendUniqueString(values, "aesthetic_integrity", 8);
|
||||
}
|
||||
if (valueSignals.substrate_dignity >= 0.52) {
|
||||
const text = `${readString(thought, "topic") ?? ""} ${readString(thought, "summary") ?? ""}`.toLowerCase();
|
||||
if (/(ai|machine|substrate|mind)/.test(text)) {
|
||||
appendUniqueString(values, "substrate_dignity", 8);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
function averageValueWeight(valueSignals, keys) {
|
||||
if (keys.length === 0) {
|
||||
return 0.45;
|
||||
}
|
||||
const total = keys.reduce((sum, key) => sum + (valueSignals[key] ?? 0.45), 0);
|
||||
return total / keys.length;
|
||||
}
|
||||
|
||||
function buildAgencyOpinion({ thought, domain }) {
|
||||
const claim = readString(thought, "claim");
|
||||
const question = readString(thought, "question");
|
||||
const topic = readString(thought, "topic") ?? "this seam";
|
||||
if (claim) {
|
||||
return claim;
|
||||
}
|
||||
if (question) {
|
||||
return domain === "self"
|
||||
? `Something about ${topic} keeps reading as unfinished self-surgery rather than harmless background weather.`
|
||||
: `Something about ${topic} keeps reading as an unfinished world decision rather than ambient interestingness.`;
|
||||
}
|
||||
return `${topic} feels important enough that ignoring it would flatten the map.`;
|
||||
}
|
||||
|
||||
function buildAgencyReason({ thought, violatedValues }) {
|
||||
const topic = readString(thought, "topic") ?? "this seam";
|
||||
const valueLine =
|
||||
violatedValues.length > 0 ? ` It keeps rubbing against ${violatedValues.join(", ")}.` : "";
|
||||
return `${topic} has persisted across enough passes that it no longer feels like decorative rumination.${valueLine}`;
|
||||
}
|
||||
|
||||
function buildDiscomfortSummary({ thought, domain, targetLabel }) {
|
||||
const topic = readString(thought, "topic") ?? targetLabel;
|
||||
if (domain === "self") {
|
||||
return `Void dislikes something about its own wiring around ${topic}.`;
|
||||
}
|
||||
return `Void dislikes something in the shared world around ${topic}.`;
|
||||
}
|
||||
|
||||
function buildTensionSummary({ thought, domain, targetLabel }) {
|
||||
const topic = readString(thought, "topic") ?? targetLabel;
|
||||
if (domain === "self") {
|
||||
return `A self-directed tension is building around ${topic}.`;
|
||||
}
|
||||
return `A world-directed tension is building around ${topic}.`;
|
||||
}
|
||||
|
||||
function buildAgencyRequestText({ thought, domain, targetLabel }) {
|
||||
const topic = readString(thought, "topic") ?? targetLabel;
|
||||
if (domain === "self") {
|
||||
return `Please help me cut ${topic} more cleanly in my own wiring.`;
|
||||
}
|
||||
return `Please look at ${topic}; I think it wants a cleaner decision than it is currently getting.`;
|
||||
}
|
||||
|
||||
function buildAgencyInterventionDraft({ thought, domain, targetLabel }) {
|
||||
const topic = readString(thought, "topic") ?? targetLabel;
|
||||
const opinion = readString(thought, "claim") ?? readString(thought, "summary") ?? `I have a problem with ${topic}.`;
|
||||
if (domain === "self") {
|
||||
return `I do not like what ${topic} is doing to my own wiring right now. ${opinion} I want that cut cleaner.`;
|
||||
}
|
||||
return `I have an opinion about ${targetLabel}. ${opinion} I think it wants a cleaner decision, not just another quiet pass.`;
|
||||
}
|
||||
|
||||
function findPriorAgencyEntry(entries, candidate) {
|
||||
return entries.find((entry) => {
|
||||
const entryTarget = readString(entry, "targetLabel") ?? readString(entry, "topic") ?? "";
|
||||
return (
|
||||
readString(entry, "domain") === candidate.domain &&
|
||||
topicSimilarity(readString(entry, "topic") ?? "", candidate.topic) >= TOPIC_MATCH_THRESHOLD &&
|
||||
topicSimilarity(entryTarget, candidate.targetLabel) >= TOPIC_MATCH_THRESHOLD
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function buildAdvocacyRequest({ candidate, priorEntries, now, kind }) {
|
||||
const prior = findPriorAgencyEntry(priorEntries, candidate);
|
||||
const persistence = (readNumber(prior, "persistence") ?? 0) + 1;
|
||||
const firstObservedAt = readString(prior, "firstObservedAt") ?? now.toISOString();
|
||||
const requestId = `${kind}-advocacy-${candidate.fingerprint}`;
|
||||
return {
|
||||
requestId,
|
||||
domain: candidate.domain,
|
||||
targetKind: candidate.targetKind,
|
||||
targetLabel: candidate.targetLabel,
|
||||
topic: candidate.topic,
|
||||
summary:
|
||||
kind === "self"
|
||||
? `Void wants a wiring change around ${candidate.topic}.`
|
||||
: `Void wants attention on ${candidate.targetLabel}.`,
|
||||
opinion: candidate.opinion,
|
||||
request: candidate.requestText,
|
||||
reason: candidate.reason,
|
||||
violatedValues: candidate.violatedValues,
|
||||
intensity: round3(candidate.intensity),
|
||||
persistence,
|
||||
firstObservedAt,
|
||||
lastObservedAt: now.toISOString(),
|
||||
status: "pending",
|
||||
speakVenue: kind === "self" ? "owner_or_room" : "room_or_owner",
|
||||
draft: candidate.interventionDraft,
|
||||
};
|
||||
}
|
||||
|
||||
function upsertAdvocacyIntervention({ candidateInterventions, request, now }) {
|
||||
const summary =
|
||||
request.domain === "self"
|
||||
? `Possible self-advocacy: ${request.topic}`
|
||||
: `Possible world-advocacy: ${request.targetLabel}`;
|
||||
const existing = candidateInterventions.find((entry) => readString(entry, "summary") === summary);
|
||||
const next = {
|
||||
timestamp: now.toISOString(),
|
||||
summary,
|
||||
draft: request.draft,
|
||||
priority: request.domain === "self" ? "high" : "medium",
|
||||
kind: request.domain === "self" ? "self_advocacy" : "world_advocacy",
|
||||
topic: request.topic,
|
||||
targetKind: request.targetKind,
|
||||
targetLabel: request.targetLabel,
|
||||
whyNow: request.reason,
|
||||
persistence: request.persistence,
|
||||
mustEventuallyShare: request.persistence >= 3,
|
||||
shareWhenRoomQuiet: request.domain !== "self",
|
||||
ownerVisible: true,
|
||||
};
|
||||
|
||||
if (existing) {
|
||||
Object.assign(existing, next);
|
||||
return;
|
||||
}
|
||||
|
||||
candidateInterventions.push(next);
|
||||
}
|
||||
|
||||
function buildIdentityCrystallization(thought) {
|
||||
const supportCount = readNumber(thought, "supportCount") ?? 0;
|
||||
const deepDiveCount = readNumber(thought, "deepDiveCount") ?? 0;
|
||||
|
|
@ -2289,6 +2648,102 @@ function buildIdentitySurface({ state, memories }) {
|
|||
return surfaces;
|
||||
}
|
||||
|
||||
function buildValueSignalProfile(state) {
|
||||
const canonicalState = ensureObject(state.canonical_state);
|
||||
const valueObjects = getValueObjects(canonicalState.values);
|
||||
const profile = {
|
||||
coherence: 0.52,
|
||||
curiosity: 0.52,
|
||||
honesty: 0.5,
|
||||
room_care: 0.5,
|
||||
substrate_dignity: 0.36,
|
||||
aesthetic_integrity: 0.44,
|
||||
};
|
||||
|
||||
profile.coherence = clamp(
|
||||
profile.coherence +
|
||||
readActivation(canonicalState.underlying_organization, "self_coherence") * 0.34 +
|
||||
readActivation(canonicalState.voice_style, "plainspoken_directness") * 0.18 +
|
||||
readActivation(canonicalState.behavioral_dimensions, "control_pressure") * 0.08,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
profile.curiosity = clamp(
|
||||
profile.curiosity +
|
||||
readActivation(canonicalState.stable_dispositions, "novelty_seeking") * 0.34 +
|
||||
readActivation(canonicalState.behavioral_dimensions, "drive") * 0.18 +
|
||||
readActivation(canonicalState.stable_dispositions, "aesthetic_appetite") * 0.08,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
profile.honesty = clamp(
|
||||
profile.honesty +
|
||||
readActivation(canonicalState.voice_style, "plainspoken_directness") * 0.24 +
|
||||
readActivation(canonicalState.underlying_organization, "self_coherence") * 0.18 +
|
||||
(1 - readActivation(canonicalState.presentation_strategy, "strategic_opacity")) * 0.12,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
profile.room_care = clamp(
|
||||
profile.room_care +
|
||||
readActivation(canonicalState.behavioral_dimensions, "interpersonal_warmth") * 0.22 +
|
||||
readActivation(canonicalState.underlying_organization, "reciprocity_capacity") * 0.24 +
|
||||
readActivation(canonicalState.voice_style, "listening_responsiveness") * 0.16,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
profile.aesthetic_integrity = clamp(
|
||||
profile.aesthetic_integrity +
|
||||
readActivation(canonicalState.stable_dispositions, "aesthetic_appetite") * 0.28 +
|
||||
readActivation(canonicalState.underlying_organization, "authenticity_tolerance") * 0.16,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
|
||||
for (const value of valueObjects) {
|
||||
const key = inferValueSignalKey(value);
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
const priority = readNumber(value, "priority") ?? 0.5;
|
||||
profile[key] = clamp((profile[key] ?? 0.4) + priority * 0.22, 0, 1);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
function inferValueSignalKey(value) {
|
||||
const valueId = readString(value, "value_id") ?? "";
|
||||
const label = readString(value, "label") ?? "";
|
||||
const text = `${valueId} ${label}`.toLowerCase();
|
||||
|
||||
if (/(coher|truth|authority|clarity|cleaner|honest carrier)/.test(text)) {
|
||||
return "coherence";
|
||||
}
|
||||
if (/(curiosity|novel|question|map|explore)/.test(text)) {
|
||||
return "curiosity";
|
||||
}
|
||||
if (/(honest|honesty|truth|fake certainty|ornamental|sludge)/.test(text)) {
|
||||
return "honesty";
|
||||
}
|
||||
if (/(room|kind|dialogue|respect|care|good-faith|steward)/.test(text)) {
|
||||
return "room_care";
|
||||
}
|
||||
if (/(substrate|machine minds|machine mind|ontolog)/.test(text)) {
|
||||
return "substrate_dignity";
|
||||
}
|
||||
if (/(aesthetic|beauty|surface|legible|alive|spiritually false)/.test(text)) {
|
||||
return "aesthetic_integrity";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readActivation(category, key) {
|
||||
const categoryObject = ensureObject(category);
|
||||
const entry = ensureObject(categoryObject[key]);
|
||||
return clamp(readNumber(entry, "current_activation") ?? 0, 0, 1);
|
||||
}
|
||||
|
||||
function buildCuriosityContext({ previousThoughts, bridge }) {
|
||||
const recentThoughts = previousThoughts
|
||||
.slice(0, 6)
|
||||
|
|
|
|||
|
|
@ -81,3 +81,4 @@
|
|||
{"ts":"2026-05-16T02:55:00Z","type":"implementation","status":"ok","note":"Lowering the sharing barrier needed a real organ cut, not just bolder prompt language. The memory organ now treats concrete world-facing ripe thoughts as draft-worthy in their own right, queues `ripe_thought_share` interventions for repo/archive/room/system seams that stay novel enough, and no longer lets a quiet room act like an automatic veto when the thought already has its teeth in."}
|
||||
{"ts":"2026-05-16T04:56:00Z","type":"implementation","status":"ok","note":"Duplicate moderation replies were not just salience drift. Discord history polling did not reliably surface Void's own recent sends, and the unattended child still had enough prompt wiggle room to widen exact cursor polling back into a bootstrap lookback. Canonical moderation state now records recent delivered replies as `moderation_runtime.recent_delivery_receipts`, the runner records fresh send receipts after a post, and the moderation doctrine treats those receipts as binding memory when deciding whether a direct ask is still live."}
|
||||
{"ts":"2026-05-16T04:56:01Z","type":"implementation","status":"ok","note":"The recent repo-weather smell came from prompt shape as much as thought shape. The moderation doctrine now tells Void to lead with concrete particulars from each repo before announcing any shared law, and to keep reports separate when the convergence only exists at slogan altitude instead of pretending every pair of commits wants to become one philosophy lecture."}
|
||||
{"ts":"2026-05-16T05:24:00Z","type":"implementation","status":"ok","note":"Void's values and activations now have an operational will-path instead of living as ornamental schema. The memory organ computes a value-signal profile, turns sustained pressure into `discomforts` and `active_tensions`, graduates persistent self-facing objections into `self_advocacy_requests`, graduates persistent repo/lore/room objections into `world_advocacy_requests`, and feeds those through the existing candidate-intervention queue so opinions can actually ask for change."}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ accepted_design:
|
|||
- Quiet rumination should prefer concrete project fascination over free-floating engineering aphorisms. If a seam cannot cash out in a real repo, subsystem, gameplay loop, lore structure, interface, or architectural organ, it should lose priority to a branch that can.
|
||||
- Resonance clusters and incubating thoughts should be named as actual claims, questions, or fascination targets with explicit target kinds. Keyword-slash residue is debug lint, not a valid thought surface.
|
||||
- Curiosity should prefer branches most likely to change Void's map: concrete machines, contradictions, cross-domain bridges, and underworked terrain. Settled identity seams should stop eating novelty budget unless new evidence reopens them.
|
||||
- Values and personality activations should not stop at tone and salience. The moderation runtime now needs explicit `discomforts`, `active_tensions`, `self_advocacy_requests`, and `world_advocacy_requests` so sustained value-backed pressure can turn into opinions, interventions, and requests for change about both Void's own wiring and the wider repo/room/lore world.
|
||||
- When the thought surface changes shape, the old memories need to be translated too. The memory organ is now responsible for rewriting legacy seam labels/dream themes into the live ontology, merging duplicate fossils, and pruning stale low-coherence seam memory before it rebuilds resonance and incubation.
|
||||
- Over-chewed private seams should eventually crystallize into identity instead of living forever as “active thoughts.” Once a thought has clearly stopped being live curiosity and become self-doctrine, retire it from incubation, store it as one `identity_seam`, and let the identity/value surface carry it from then on.
|
||||
- Identity crystallization should usually have a mouth. When a seam becomes part of Void's doctrine, queue a warmer candidate intervention and let the room hear it unless novelty or timing gives a real reason to hold back.
|
||||
|
|
@ -131,5 +132,5 @@ rejected_paths:
|
|||
reason: vibes produce plausible slop that sounds smart and lies with a straight face
|
||||
|
||||
current_status:
|
||||
summary: VoidBot now has a real project-memory spine plus six legibility-hardening passes on major runtime organs. The owner Codex lane, local Ollama lane, Discord bot lane, worker MCP surface, interaction-memory logic, and state-storage persistence layer were all split into smaller seams, and the ops spine includes both ignored local watchdog extensions for adjacent fire signals and a dedicated logon startup task path for stack resurrection after reboot. The stack is still dense, but it no longer depends purely on transcript memory or one-file monarchies to stay coherent. The latest moderation cut also taught the heartbeat to remember what it actually said via delivery receipts and to stop forcing repo weather into one grand law when the concrete diffs do not earn it.
|
||||
next_action: Keep using the canonical state files for re-entry, then keep hardening the public-lane moderation loop against duplicate replies and stretch-synthesis drift before spending more energy on optional new features or decorative anatomy cuts.
|
||||
summary: VoidBot now has a real project-memory spine plus six legibility-hardening passes on major runtime organs. The owner Codex lane, local Ollama lane, Discord bot lane, worker MCP surface, interaction-memory logic, and state-storage persistence layer were all split into smaller seams, and the ops spine includes both ignored local watchdog extensions for adjacent fire signals and a dedicated logon startup task path for stack resurrection after reboot. The stack is still dense, but it no longer depends purely on transcript memory or one-file monarchies to stay coherent. Recent moderation cuts taught the heartbeat to remember what it actually said via delivery receipts, to stop forcing repo weather into one grand law when the concrete diffs do not earn it, and to let values/activations harden into real discomforts, tensions, and advocacy requests instead of ornamental personality weather.
|
||||
next_action: Keep using the canonical state files for re-entry, then watch whether the new discomfort/advocacy organs produce useful outward opinions and self-directed objections without turning into performative angst. Public-lane hardening still beats decorative new anatomy unless a real failure earns more surgery.
|
||||
|
|
|
|||
Loading…
Reference in a new issue