feat(tension): persistent cognition substrate — tension store, salience-gated cycles, thread injection (PN-95) - #96
Merged
Conversation
…-95)
Layers 1 and 2 of the persistent cognition substrate. The claim is that
"something wouldn't leave me alone" is a property of a state object with
the right update law, not of a process with the right uptime: between
cycles the entity's state currently changes only if an LLM call changes
it, so every inter-cycle transition has to be paid for in tokens and in
practice none happen. This is the missing arithmetic layer.
Layer 1 — tension store (`src/tension/`), persisted to
`{entity}/tension.json` with the same discipline as predictions.json:
atomic rename, in-process mutex plus flock, and a fail-closed loader in
the write path that quarantines rather than overwriting.
The two non-negotiable properties are enforced structurally, not by
convention:
* No time-decay to zero. `tick` has no decay term and no discharge term
at all — it can only add — so an untouched thread gets louder. Accrual
is integrated over wall time, so a restart neither loses pressure nor
double-counts it, and a long outage resumes bounded.
* Discharge requires work, not attention. The only subtracting method
demands a WorkArtifact, and the only minter of one checks the claim
against something outside the text: a file whose mtime moved outside
the journal, a prediction that actually resolved, or a tool that
actually ran. Refused claims are alerted, never silently dropped — an
ignored claim reads exactly like a granted one. Mentions move an
observational counter and nothing else; in particular they do not move
`last_touched`, so a thread cannot be kept quiet by being written about
either. Spec §7 risk 3 names this the most likely failure; it is
covered by tests that assert prose changes nothing.
Ingest is narrow (§7 risk 2): prediction errors above the surprise
threshold, and explicit [THREAD:] markers. Threads carry self-contained
content so they survive the fold that eats their referent (§8 Q4).
Retirement is tombstoned with a reason and retained (§8 Q3).
At `max_live_threads` the store admits the thread and raises a triage
demand carrying candidates. It does not drop the newest (the intent
queue's push defect) or the oldest (the predictions prune defect); this
is the third store and it does not reproduce them a third time.
Layer 2 — `SalienceEval` gates a cycle on max tension, declining vigil
signals, or the floor interval. The floor clause is a starvation guard
and is mandatory: without it a quiet period silently becomes a period of
no cognition and looks healthy from inside because nothing is reporting.
A floor of 0 therefore means "always fire", not "guard disabled" — a
misconfiguration fails toward running, never toward silence.
The §3 discriminator is implemented and emitted per cycle: Spearman rho
between tension-ordering and recency-ordering (Pearson over average
ranks, so ties stay correct), plus the reach tally. Both are exposed via
`pulse-null status` along with what the entity is carrying (§8 Q2).
Layer 3, the payoff. At cycle start the prompt receives the top-k threads by tension rather than a positional slice of a journal file, so what the entity picks up is chosen by an accumulator it cannot edit in prose rather than by whatever survived the last fold. A thread's tension is immune to folding because it lives outside the folded document. The block goes into `build_task_system_prompt_budgeted`, which `execute_task` and `execute_intent` rebuild on every fire — deliberately NOT into the boot-time system prompt. `AppState.system_prompt` has no writer, so a thread list assembled there would be frozen at whatever the store held the last time systemd restarted the service, which is strictly worse than the document reading this replaces. Tier High, not Low: under budget pressure the block may be truncated, but dropping it outright would hand selection straight back to document layout, which is the defect Layer 3 exists to remove. The §3 metrics — rho against recency, and the reach count — ship inline in this routine payload rather than as a separate quality report, because a coverage denominator that ships separately does not get read. An outstanding triage demand rides along in the same block, so the cap obligation reaches the entity in the same breath as the threads it is about. The autonomy context documents the three marker grammars and states the artifact rule where the entity will actually read it: discharge needs a file changed outside the journal, a prediction resolved, or a tool that ran; writing about a thread does not lower its tension; a give-up needs a reason, is kept as a tombstone, and is visible to D.
…-cognition-substrate # Conflicts: # src/config/mod.rs # src/plugins/manager.rs # src/server/auth.rs # src/server/e2e_tests.rs # src/server/prompt.rs # src/server/trust.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #95.
Spec:
/opt/pulse-vault/pulse-null/specs/in-progress/persistent-cognition-substrate-spec.md(v1.0, D + Echo).Merge order: after PN-94 (interest-triggered outreach) per spec §9 — this store is the Phase 2 trigger for that channel; outreach ships first so tension has an outlet.
What's in
src/tension/): arithmetic-only per-tick update, zero LLM. No decay to zero (untouched threads get louder); discharge requires a verifiedWorkArtifact(file diff outside the journal / resolved prediction / tool rounds) — text mentions cannot discharge, and cannot even movelast_touched(mentions feed a separate observational counter). Refused work claims raise an alert instead of being silently dropped. Persisted at{entity}/tension.json, flock-guarded fail-closed; forward-incompatible files quarantine loudly.SalienceEvalgates cycle spend on max tension, vigil decline, or the mandatoryfloor_intervalstarvation guard (0fails toward always-fire). Registered as evaluator"salience"— opt-in; no schedule.json task uses it yet, so nothing changes behaviour until a task is pointed at it.[tension]config exactly per spec §5 (constants marked INITIAL GUESS); triage-not-drop atmax_live_threads; tombstoned abandonment with reason; store readable viapulse-null status.Deliberate deviations (details in commit bodies)
Unknownserde catch-alls onThreadOrigin/ThreadResolution— unknown variants hit the fail-closed quarantine instead of half-parsing.Threadcarriescontent,resolution_reason, and §3 bookkeeping beyond the §2.1 field list (required by §8 Q3/Q4 and the metrics).ResolutionVerdictinput type makesAnswered/Supersededunconstructible without an artifact.Verification
scripts/gate.shPASS;cargo clippy --features discord-text --all-targets -- -D warningsclean;cargo test --features discord-text902 passed / 0 failed (~85 new); release build OK.Operational notes
ClaudeCodeProvider::supports_tools() == false) and the prediction store is 94 pending / 0 resolved, so discharge will lean on file-diff evidence — expect threads to climb and the 40-thread triage to surface sooner than the constants suggest.[scheduler] enabledso accrual continues while the schedule is paused; honours the isolation backstop.src/config/mod.rs. Name overlap note:SalienceEvalhere is the cycle gate, unrelated to PN-94'sEntityEvent::Salience.🤖 Generated with Claude Code
https://claude.ai/code/session_01EDtGQtXxs4fVK4g484Y1Fj