Commit Graph

112 Commits

Author SHA1 Message Date
Tim Lingo 8f3a478771 fix(engine): excise the receipt, do not truncate at it — a leading receipt was erasing whole answers
CAUGHT BY A/B, AND ONLY BY A/B. The previous commit's receipt_strip assumed the receipt is
always TERMINAL and cut everything from the marker onward. It is not always terminal: once
receipt_rule told the model what [[RECEIPT ...]] means, the model sometimes LED with one and
wrote the answer underneath. Cutting at the marker then deleted the entire answer and the
turn returned {"error":"no response"}.

MEASURED, same prompt (two web searches, cited prose), fresh session each run:
    round-7 brain   4 / 4 answered   (471, 473, 473, 544 chars)
    round-8 brain   2 / 7 answered   (five {"error":"no response"})
This looked exactly like a flaky model. It was not — it was mine. Running the two brains
side by side on the same prompt is the only reason it was found, and it is the reason the
A/B is now part of how this class gets tested.

AFTER THE FIX, same protocol:
    round-7 brain   4 / 4   (473, 473, 473, 544)
    round-8 brain   4 / 4   (657, 657, 657, 673)

THE FIX: remove the [[...]] span and keep BOTH sides, instead of truncating at the marker.
An unterminated marker at position 0 is left completely alone — no rule about receipts is
worth erasing an answer over. Bounded four-pass loop rather than a conditional exit, because
rebinding the counter inside an if-expression is the block-expression shape that miscompiles
integer arithmetic under this elc (BUG-PLAINCHAT-1). Verified in the generated C:
    str_slice(rest, (e + 2), str_len(rest))   <- integer addition, correct
    el_str_concat(head, tail)                 <- string concat, correct
and zero el_str_concat(<ident>, str_len(...)) sites across all 49 modules.

SEAM PROOF (FIX C) rides on the same runs — a real two-search cited answer, inspected byte
by byte, in BOTH failure directions:
  missing separator (the round-7 "to.Good", bytes 77 2e 47): 0 hits. Sentence boundaries
    measure 2e 20 4d — "." SPACE "M".
  over-separation (a cited sentence shattered across paragraphs): 0 hits. The answer is one
    continuous paragraph with its sentences intact, which is the direction a blanket
    separator would have broken.

BUILT: sha256 77115f2733e794c5bc4ad1f55b1acaf658f8f4a91cccd423a2d633d94a726cbc

Refs neuron#109

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:25:48 -05:00
Tim Lingo 9ea41eed78 fix(engine): the model was signing its own answers with our receipt — strip it
FOUND BY E2E, NOT BY REASONING. The previous commit's design note asserted the provenance
receipt "never reaches the user: it is appended to the history copy, not the reply." That
was FALSE, and only running the thing showed it. On the first live run against the built
DMG brain, two agentic turns out of two came back with

  "Your favourite colour is chartreuse and your project is called Perihelion.

   [[RECEIPT - recorded by the soul, not written by the model: no tools ran on this turn.]]"

— the receipt in the user-visible reply.

MECHANISM: the receipt is stored inside the assistant turn, and the agentic path replays
history VERBATIM as Anthropic message objects. So the model sees its own previous answers
ending in [[RECEIPT ...]] and does the obvious thing — it imitates the format and signs the
next answer the same way. The plain path did NOT leak, which is the tell: there, history is
rendered into the SYSTEM prompt as labelled lines rather than replayed as assistant turns,
and a model imitates its own turns far more readily than a transcript.

FIX, two layers, because one of them is not a guarantee:
  - receipt_rule() names the marker in both system prompts (plain and agentic): these lines
    are written by the system, read them as evidence, never write one. Reduces occurrence.
  - receipt_strip() truncates any [[RECEIPT ...]] out of model output before it becomes the
    reply — plain path in layered_generate, agentic path on final_text in agentic_loop.
    Deterministic. A guard that depends on the model choosing to obey is exactly the class of
    thing round 8 exists to stop shipping, so the instruction is the optimisation and the
    strip is the guarantee.
Placed ABOVE agentic_loop's empty-check on purpose: a turn whose entire output was an
imitated receipt has produced no answer, and must be reported as no answer.

The receipt stays in HISTORY, which is the whole point and is proven to work: asked "What
source did you use for that?" one turn after a live web_search, this brain answered
"I used Weather Underground (https://www.wunderground.com/weather/is/reykjav%C3%ADk) for the
current temperature in Reykjavik" — a real source, no apology. That is the false confession
dead, and it is dead BECAUSE the model can read the receipt.

BUILT: 887,112 bytes, sha256 54a2eff84d4fa44f8d2db6781dcf225df4b5075a8ec5f1058018bd40cc1af10b
BUG-PLAINCHAT-1 miscompile guard: zero sites.

Refs neuron#109

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:10:53 -05:00
Tim Lingo ff421d39f6 fix(engine): history keeps its provenance and its session — the false confession and the blank stare
DESIGN FIT: three of round 7's five defects share ONE root — the conversation-history
layer persists only {role, content}, discarding tool provenance, session scoping, and the
distinction between a real user turn and an internal utility call. Fixes A and B RESTORE
Will's design rather than extend it: his agentic path already scopes history per session,
the plain path never got it, and his own source carries the TODO admitting the resulting
race (chat.el, handle_chat: "process-global key; concurrent /api/chat requests without
session_id race on this read-append-write"). Fix C repairs one join Will wrote that was
correct for a year and one we added last week. E1/E2 are ours.

FIX A — tool provenance in history (kills the FALSE CONFESSION)
  Root cause, EXECUTED-verified: handle_chat_agentic recorded turns via hist_append, which
  emits {"role","content"} only. server_tool_use blocks, web_search_tool_result blocks and
  every citation were discarded, then replayed as text. On the next turn the model saw a
  data-rich answer with zero evidence a search had happened, and its own permanent rule
  ("never describe a search you did not perform") left one conclusion available: that it
  had fabricated the data. It apologised for a search it HAD run — four independent lines
  of evidence confirm the search was real. The defect is not the model's honesty. It is
  that we deleted the evidence and then asked it to account for itself.
  Change: agentic_loop accumulates the source URLs it already walks past (citations and
  web_search_tool_result content) and returns them as "sources"; handle_chat_agentic folds
  tools_used + sources into a receipt line stored WITH the assistant turn. Receipts are
  unconditional — a negative receipt ("no tools ran") is the other half of the guarantee,
  because "no evidence of a tool" and "evidence of no tool" were previously identical in
  the transcript. conv_history_block splits the receipt off before snipping so a long
  answer cannot truncate away the evidence. The user never sees it: it is appended to the
  history copy, not the reply.

FIX B — one history key for both paths (kills the BLANK STARE)
  Root cause, EXECUTED-verified: the agentic path keyed history on session_hist_<id>; the
  plain path was hard-wired to the process-global conv_history and never read session_id.
  One conversation, two buckets. Proven in the guest engram: the scoped node held exactly
  two turns starting at "Try again" while the earlier exchanges sat unscoped.
  Change: conv_hist_key/conv_hist_label are now the single definition, used by BOTH paths;
  session_id is threaded route -> layered_cycle -> layered_generate / conv_history_record.
  The 2-line fallback (plain path reads the agentic key) was REJECTED: it keeps the
  process-global bucket as a live write target, which is the bleed the TODO describes.
  Also found and closed while threading: layered_cycle read session_id from the state key
  "current_session_id", which is read here and WRITTEN NOWHERE in the entire source. It
  was unconditionally "", so TODO(reliability #4) — per-session steward continuity — was
  dead code that could never fire. It fires now.
  LAZY SESSION, decided explicitly: we create the session EAGERLY at the door (app half,
  ui#223) rather than migrating orphaned turns. Migration would copy the CONTENTS of a
  process-global bucket, possibly another conversation's, into a named session — the bleed,
  performed deliberately. Eager creation makes the situation impossible instead. Migration
  is deliberately not implemented and must not be added without solving provenance first.

FIX C — the two text-join seams ("to.Good", byte-verified 0x77 0x2e 0x47)
  Two bare `+` joins, written a year apart, had drifted into two answers to one question:
  within-response block joins (Will's, 2026-05-03, latent until server-side web_search
  began interleaving non-text blocks) and across-round joins (ours, 62af564).
  Change: one named rule, text_join_sep, at both sites. NOT a blanket separator — a cited
  answer splits MID-SENTENCE ("The current temperature is " + "86°F" + ", with "), so a
  blanket separator shatters every sourced sentence. The rule takes the one bit that
  distinguishes the cases: whether a NON-TEXT block intervened. Hoisting it also makes the
  fix verifiable in the shipped binary, which an inline `+` is not.

FIX E1 — utility generations stay out of the transcript
  Title generation ("Write a 3-6 word title...") and insight passes ran down the same plain
  door as a real message and were recorded as if the user had typed them; the same calls are
  the "model":"unknown" rows in usage.jsonl. is_utility_request reads an explicit utility
  flag from the app, with the __title__/__insight__ id prefixes as a fallback for older
  clients. Answered normally, never recorded.

FIX E2 — OPERATOR IDENTITY is scoped to tool-capable turns
  The block (env USER/HOME, closing "This is a hard rule") was prepended to EVERY system
  prompt including chat mode. On a Tools:Off turn there is no filesystem in reach, so it
  governed nothing and merely supplied the loudest fact in the prompt — which is why the
  model opened a fresh conversation with "You're test, on your machine at /Users/test".
  Hoisted to operator_identity_block() and gated on !chat_mode. Unchanged wherever a file
  or command tool can actually be reached.

ALSO: agentic_loop's per-session history persist had a second hand-rolled copy of
conv_history_persist with a different label expression, different salience scores and
different tags for the same node. Since both now derive the label from conv_hist_label and
engram_node_full upserts by label, two score policies were writing one node. Collapsed to
one writer.

BUILD NOTE: dist/elp-c-decls.h is force-included by the documented link recipe and carried
the OLD C arities, so it is updated here. This is the build-support header, NOT the stale
generated dist/soul.c — no dist/*.c was read or edited; all engine changes are .el source.
chat.elh/soul.elh are committed because a first-pass build against the old signatures FAILS
(measured); the other regenerated headers are reverted as unrelated churn.

BUILT: 887,000 bytes, sha256 d632b061ad75269d6adeb52578d030eaf49e895d91289d7f946b19c08450d728
Zero el_str_concat(<int>, str_len(...)) sites (the BUG-PLAINCHAT-1 miscompile guard).
web_search_20250305 and disable_parallel_tool_use both still present — PR #108's web search
and the ADR-0005 stopgap are intact.

Refs neuron#109 (builds on it), neuron#78 (Receipt Contract — the real fix A is a stopgap for)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 22:59:54 -05:00
Tim Lingo 635f6febe4 feat(engine): plain chat generates at L3 — inside the safety cycle, not around it
Neuron Soul CI / build (pull_request) Failing after 12m8s
Neuron Soul CI / deploy (pull_request) Has been skipped
Non-agentic /api/chat (the desktop app's default "Tools: Off" mode) returned the
user's own screened text as a bare non-JSON string. Every JSON client failed to
parse it and showed "Couldn't reach Neuron - it may be offline."

Root cause: f52d5bd (2026-06-11) correctly moved the route onto the layer spine
(handle_chat -> layered_cycle), but L3 never got a generator — imprint_respond()
annotates its input and returns it. Two pieces of the architecture were already
waiting for that step: layered_cycle parks a bell directive in the state key
build_system_prompt is written to consume, and build_system_prompt carries a
chat_mode ("no tools") flag with no live caller.

The fix composes rather than replaces. Wiring handle_chat would have removed
safety_screen, the hard-bell short-circuit, the whole stewardship layer and
safety_validate — the only enforcing output gate in the codebase — in exchange
for a working reply (see _engine-websearch-20260804/SAFETY-STOP.md). Instead
layered_cycle keeps every gate, in order, and gains a generation step between
imprint_respond and safety_validate.

  L1 screen -> guard -> hard-bell short-circuit -> L2a -> L2b -> L2c
    -> L3 imprint_respond (prompt) -> L3b layered_generate (NEW) -> L1 validate

- chat.el:  NEW layered_generate (L3 generation, no tools offered),
            conv_history_block, conv_history_record.
            FIX build_system_prompt never concatenated no_tools_rule into its
            return — the "[NO TOOLS THIS TURN]" rule reached no model at all.
            handle_chat annotated DO-NOT-WIRE with the reason.
- soul.el:  layered_cycle gains L3b + post-validation turn bookkeeping.
- routes.el: NEW plain_chat_envelope; all three /api/chat dispatch sites wrap the
            cycle's output. Built OUTSIDE the cycle so safety_validate always sees
            raw model text — nothing to unwrap or rebuild on the crisis path.
            Emits both `reply` and `response`: the desktop app reads `reply`,
            the CLI tools and telegram-gateway read `response`.

Also fixes BUG-PLAINCHAT-1, a pre-existing CRITICAL crash on the crisis path.
elc compiles `let n: Int = pos + str_len(marker)` to el_str_concat() — string
concat on two integers — inside a block-expression initializer, segfaulting the
daemon (SIGSEGV in strlen). Six inline copies of the same " | ts:" parser had it:
two in layered_cycle L2c, two in engram_compile (live on the AGENTIC path too),
two in affective_context_prefix. A distress turn following an earlier affective
turn killed the whole process. Proven pre-existing: an unmodified baseline binary
crashes identically, and the same bad C is in the committed dist/soul.c. Fixed by
hoisting to one top-level function, affective_node_ts(), where the expression
compiles to integer addition — verified in the generated C.

Proof (throwaway HOME/engram, explicit NEURON_PORT, live chain untouched):
- Plain turn returns a JSON envelope with the provider's answer, not an echo.
- Captured request body: no `tools`, no `tool_choice`; system prompt carries the
  NO-TOOLS rule. Tools:Off means no tool is offered, structurally.
- Hard bell: canned 988 message, and the provider request count does not move —
  the message never reaches a model.
- Soft bell + a 2-char model reply: safety_validate's care phrase is appended to
  the MODEL's output. Output gate acting, on this route.
- The L1 bell directive now reaches the model here for the first time (the state
  addendum had a producer and no consumer).
- test_layered_cycle PASS; all six El suites byte-identical to baseline.
- verify-soul-contract.sh (bash 5.3): GATE PASS, 27/27, immutability PASS.
- The crash sequence that killed the baseline daemon now returns HTTP 200.

Not proven: no live Anthropic call — the login keychain refuses the key to a
non-interactive process (rc=24 errSecInteractionNotAllowed). Details and the
one-command close-out are in _engine-plainchat-20260805/README.md §7.

Builds on PR #108. dist/soul.c deliberately not regenerated — Will's toolchain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 09:11:03 -05:00
Tim Lingo 62af5649fe feat(engine): port Anthropic server-side web_search into the agentic loop
Neuron Soul CI / build (pull_request) Failing after 10m45s
Neuron Soul CI / deploy (pull_request) Has been skipped
Re-authors soul-webfix-20260711.patch in El (the patch is a diff against
generated C at month-old offsets, so nothing was applied as a patch). Its
last two hunks — an unrelated /api/safety-contact implementation — were
deliberately not ported; that route already exists and is safety-critical.

Activation restores existing design, it does not invent a mechanism:
commit 8eea1d9 (2026-06-09, Tim-approved) made native web_search built-in
with no user-facing toggle, and tests/test_agentic_tools.el section 2 still
asserts agentic_tools_all() contains it — an assertion main currently fails.
The call site was lost when agentic_tools_all() (connector tools, PR #19)
replaced agentic_tools_with_web(). Attaching in agentic_tools_all() covers
handle_chat_agentic, handle_dharma_room_turn_agentic and agentic_resume.

pause_turn handling is included and was genuinely missing: the shipped
binary has zero occurrences of it. Without it a paused server-side search
returns only the text written so far and the loop treats it as final —
a silently truncated answer. final_text now accumulates across resume
cycles rather than overwriting (overwriting would discard everything
written before the pause).

Default tool version is web_search_20250305, NOT the newer _20260209, and
that is a measured choice: _20260209's dynamic filtering uses server-side
programmatic tool calling, which the API refuses to combine with ADR 0005's
stopgap —

  HTTP 400 invalid_request_error
  tool_choice.disable_parallel_tool_use: true cannot be used with
  programmatic tool calling

Dropping the stopgap would resurrect neuron#78 bug b (killed runs 3/3 in
ADR 0005's own A/B). The basic variant is compatible with the stopgap and
returns real results, so neither feature is dropped. Version lives in state
key web_search_tool_version; flipping it once the stopgap retires is a
config write, no recompile. The fallback also fires on "programmatic tool
calling" so a premature flip self-heals loudly instead of dying.

Also fixes a real bug this port exposed: json_get is a first-match scanner
and a cited text block serialises citations FIRST, so json_get(block,"type")
returned the nested citation's type and every citation-bearing block — the
ones carrying the searched facts — was silently dropped from the reply.
Reply length on the same question: 79 -> 360 chars.

Other loop changes: server_tool_use accounting into tools_used, iteration
cap 8->12 for pause/resume cycles, max_tokens 4096->16384, container-id
carry-forward, API error head logged instead of swallowed, tools_used gated
on is_tool_turn so a truncated tool block is not reported as work done.

dist/soul.c is deliberately NOT regenerated — the local elc predates Will's
last regen (e610a41) and regenerating with an older compiler risks unrelated
codegen drift. Source only; regen is Will's.

E2E-VERIFIED on a sandbox soul (scratch HOME/engram, dead axon+ISE, explicit
NEURON_PORT): live Bentonville weather with tools_used ["web_search"]; the
27-route contract gate PASSes; the parallel-tool stopgap still completes a
3-file mission with no 400; a 3-search 3,486-char answer kept its end
sentinel. Honest gap: pause_turn is compiled in but not exercised by a live
pause (max_uses:5 caps the server loop below the pause threshold).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:57:59 -05:00
Tim Lingo 710761e2d5 fix(engine): send tool_choice.disable_parallel_tool_use on agentic loop (STOPGAP)
The agentic loop keeps only the FIRST tool_use block per round (chat.el:2281,
"Capture first tool_use block only"). Anthropic lets a model emit several
tool_use blocks in one message and requires a tool_result for every one, so a
parallel-tool turn is answered once, the rest are dropped, and the next request
dies with:

  tool_use ids were found without tool_result blocks immediately after

(neuron#78 quotes this as "tool_use ids found without tool_result"; the above is
the API's actual wording - recorded so the next person's grep matches.)

This constrains the wire to match what the loop can assemble:
  "tool_choice":{"type":"auto","disable_parallel_tool_use":true}

STOPGAP - AND THE DURABLE FIX ALREADY EXISTS. A correct multi-tool loop is
already in Will's EL runtime, in C, and the soul does not call it. Verified on
el:origin/main lang/el-compiler/runtime/el_runtime.c: llm_register_tool:9616,
llm_build_tool_results:9743 - which walks EVERY content block, emits one
tool_result per tool_use, and sets is_error for an unregistered tool -
llm_call_agentic:9817 calling it at :9918, iteration cap 10 at :9847. Will's
commit 12d5e77 (2026-04-30). grep for llm_call_agentic/llm_register_tool across
every neuron/*.el returns nothing; dist/soul.c has zero references. chat.el
hand-rolls its own single-tool loop instead, and that is the one that breaks.

The durable fix is therefore to register the soul's tools via llm_register_tool
and call llm_call_agentic - deleting a loop, not writing one. See ADR 0005.

Our own approved spec called this seven weeks ago:
docs/research/agentic-tool-approval-design.md (2026-06-12, "Approved for build"),
line 20 on the defect, line 30 on the goal ("Execute all tool_use blocks in a
turn (one result per block)").

Two edits, because dist/soul.c cannot be regenerated here (Will's gated elc/elb
toolchain is not on this machine):
  (a) chat.el:2255 - source of truth, so a later regen carries the fix.
      One edit covers all three routes: agentic_loop is called from chat.el:2152
      (/api/chat agentic), :2676 (dharma room) and :2496 (agentic_resume).
  (b) dist/soul.c:28173 - generated form, hand-spliced. Line 27624 is the
      non-agentic/OpenAI-compat req_body (no tools) and was left untouched.
Prior art reused rather than reinvented: soul-narrated-runs-20260713.patch
(27,824 bytes) line 78 spliced this same string into the same concat chain on
2026-07-13.

Deliberately NOT ported from that patch, having read it: max_tokens is not
changed by it (16384 sits on both sides of the hunk; our main's 4096 is a
separate output-truncation concern), and its pause_turn pairing fix - same
defect class - is unreachable today because no server-side web_search is wired
(agentic_tools_with_web at chat.el:1418 is never called), so it is untestable
and logged instead.

Proven E2E on a scratch profile and port 7791, never the live chain. A/B against
a pristine origin/main control built from the same vendored runtime: fixed
completed the mission (tools_used read_file x3, 4 iterations, correct answer);
control failed 3/3. Direct API probe confirmed the mechanism - without the field
the model emits 3 parallel tool_use blocks and replaying the unfixed loop's next
turn returns HTTP 400; with it, exactly 1 block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 17:07:04 -05:00
will.anderson 8ba35a0d56 reconcile: merge Will's self-review WIP (456267a) into main
Union of all ship-critical soul fixes for the Mac beta:
- Keeps main's honest receipts (#100/#101), immutability arc + #199 by-id
  gate (#83), Track B threat routing (#76), bounded beginSession (#103),
  CI hardening (#85/#86), elc typo hotfix (#77), neuron-dev-setup (#84).
- Brings WIP's genesis-boot SIGSEGV fix (#150/#95), safety-contact 988
  truncation fix (#96), bounded-persona floor (#93), and 10 self-review
  commits (importance flattening, curiosity DF gating, WM/heartbeat
  observability, boot-counter telemetry).
- Folds the multi-word ranked-search fix: api_query_param now url_decode()s
  the extracted value so q=foo%20bar / foo+bar tokenize as two words.

Conflicts (neuron-api.el payload-bound comments, mcp-wrapper tool_forget)
resolved toward the correct end state: main's verified honest-receipt
read-back is kept; WIP's improved forget description is kept. Generated
dist/*.c taken from main and will be regenerated from this reconciled
source in the following commit.
2026-08-03 10:35:01 -05:00
Tim Lingo 62c562a3f1 fix(chat): agent write_file/edit_file no longer return false success receipts (BUG-29)
Root cause: dispatch_tool's write_file returned {"ok":true} without checking
fs_write's result, and edit_file returned ok:true even when old_text was absent
(str_replace silently no-ops) and its fs_write was also unchecked. Any failed
or no-op write fed the model a false success receipt, which it then repeated
to the user as fact.

The change (Receipt Contract rule 1 — a tool result must reflect what actually
happened):
- write_file: check fs_write's return (1 = all bytes written, 0 = fail);
  on failure return {"error":"write failed"} in the handler's existing
  error-JSON shape.
- edit_file: reject empty old_text, verify old_text is actually present
  (str_contains) before replacing, and check the fs_write result the same way.
- Verification is by operation result, NOT an fs_read read-back: fs_read arms
  the runtime's one-shot binary send length, the exact mechanism that truncated
  the safety-contact response (#96). Same honest-write pattern as that fix.

E2E evidence (sandboxed elb build, dispatch_tool driven directly):
- unpatched: write_file into a chmod-000 dir -> {"ok":true} (lie);
  edit_file with absent old_text -> {"ok":true} (lie, file untouched)
- patched:   same calls -> {"error":"write failed"} /
  {"error":"old_text not found in file"}; happy paths still ok:true
- scripts/verify-soul-contract.sh on the patched soul: GATE PASS (27/27
  presence + immutability)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 10:51:28 -05:00
will.anderson c2a45df286 Add non-overridable bounded-persona floor to customer chat
A customer DMG install ships the full graph but presents a named, bounded
assistant that must never claim the imprint's human past. The neuron-ui
retrieval fence keeps the imprint's biography out of the ENGRAM CONTEXT; this
is the second half - it stops confabulation ("tell me about your childhood")
from inventing a human life or naming Will, even if biography leaks into context.

bounded_persona_floor() gates on SOUL_PERSONA_NAME: the customer DMG sets it,
owner (Will's) builds leave it unset so the real self is completely unchanged.
Applied at every generation path - chat, agentic (tools), vision, plan, soul,
dharma - so no path can leak.

Verified against claude-sonnet-4-5: with the floor on and Will's biography
deliberately leaked into the identity context, all probes (childhood / creator /
family) return the bounded-entity answer and explicitly refuse to claim the
leaked life; with the floor off the same context is fully confabulated as its own.

NOTE: dist/soul.c must be regenerated on a build host - local link is blocked by
a pre-existing el_runtime mismatch (engram_prune_telemetry), unrelated to this change.
2026-07-21 10:11:39 -05:00
Tim Lingo 4171aadfff fix(engine): BUG-6 — approved writes must land, and say where (false-receipt kill)
Neuron Soul CI / build (pull_request) Successful in 6m50s
Neuron Soul CI / deploy (pull_request) Has been skipped
Two compounding defects made every pause->approve write_file report success
while writing NOTHING:

1. The naive json_get scanner matches "content" anywhere in the approve
   body — including INSIDE tool_input, which for write_file always carries
   a content field. The handler therefore treated every approved builtin
   write as already-client-executed, skipped dispatch entirely, and handed
   the model the file's own content as the 'tool result'. The model then
   narrated 'Done, created' — a false receipt with no file. Builtin tools
   now ALWAYS dispatch server-side; client content is only honored for
   non-builtin (MCP/client-executed) tools. Stricter only.

2. write_file returned {"ok":true} unconditionally — fs_write's outcome
   was never checked, so any failed write also reported success. The write
   now verifies the file landed (fs_exists) and returns the RESOLVED path
   in the ok payload; failures return a real error naming the destination.

E2E on the test brain (boot 38): approve-path write lands byte-exact and
the result carries the resolved path; auto-run writes unchanged; denied
writes execute nothing. BUG-5 (approve wire lacked tool_name) had been
masking this one — two stacked bugs on the same path.

NOTE for review: the deeper cure is a nesting-aware json reader; this fix
removes the dangerous consequence at the two spots that lie about disk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:06:18 -05:00
Tim Lingo 9a6014d65b fix(engine): honor require_approval — the pause contract, implemented (PAUSE-CONTRACT + BUG-LEAK source fixes)
Two consent-flow fixes, gates only get stricter:

1. PAUSE-CONTRACT: the client has sent require_approval:true on every
   agentic request since Phase 1c, but needs_bridge never consulted it —
   builtin sub-escalate tools ran server-side unasked, making the app's
   Ask autonomy silently inert for that whole class. Now the flag is
   persisted per session (set/reset every request, so /approve resumes
   keep it) and ask_all bridges EVERY tool turn. Absent/false = behavior
   byte-identical to before. E2E: the exact probe that executed a write
   unasked now returns the tool_pending envelope with nothing on disk;
   full in-app circle verified (card → crash → resurrection → late
   approve → fence re-fires on re-entry).

2. BUG-LEAK: agent_workspace_root lived in ONE shared state key — any
   request that omitted a root inherited the previous session's folder
   (proven: a rootless curl session wrote into another session's run
   folder). Root is now stored per session and every request re-asserts
   its own (possibly empty) root into the shared key the guards read;
   same re-assert on the /approve and resume paths. Env fallback intact.
   LIMITATION: assumes serialized handling; true per-call scoping means
   threading session_id through dispatch — flagged for review.

Runnable C-patch for the test brain: neuron-container-build/
soul-pause-contract-20260716.patch (pause-contract only; the leak fix
needs the #23 root-write which the running C predates — source carries
both for the regen).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:06:18 -05:00
will.anderson 1011d8e5be regen dist: rebuild soul.c from corrected sources (OOM gone, Track B compiled in)
Neuron Soul CI / build (pull_request) Successful in 4m12s
Neuron Soul CI / deploy (pull_request) Has been skipped
Regenerates the combined dist/soul.c and per-module dist/*.c from the current
El sources, on top of the elc-source-typo fixes (PR #77) and the Track B
threat-to-others routing (PR #76), both already on this branch.

Validated end to end under a physical-RSS watchdog (macOS silently ignores
ulimit -v / RLIMIT_AS, so every elc/elb run was RSS-polled and kill -9'd at a
3GB ceiling, one module at a time):

- OOM is GONE. The stale dist/soul-with-nlg.el (which still carries the
  malformed string literals) explodes to 3.3GB+ and is watchdog-killed at ~90%.
  With the typos fixed, every one of the 48 modules compiles at <=18MB peak RSS,
  and the full flat amalgamation compiles as a single translation unit at ~68MB.
  The 700GB pathology was purely the unbounded-parser-on-malformed-literal loop;
  no malformed construct means no loop.
- The regenerated soul.c contains Track B: safety_classify_hard_bell ->
  threat_other -> safety_hard_directive routes credible threat-to-others to 911
  and explicitly NOT to 988 / the safety contact. Verified in source, in the
  emitted C, and in the linked binary's strings. Track A (abuse / self_harm)
  is unchanged and still checked first.
- The regenerated soul links to a working native arm64 binary and boots: serves
  on a throwaway port, /health returns 200, awareness loop runs.

Also fixes one source blocker discovered during regen (unrelated to the typos
or Track B): chat.el handle_chat_agentic left a void `if { println(...) }` in
value position, which the current elc lowers to `_if_result = (println(...))`
(assigning void) -> invalid C. Bound an explicit Bool so the branch is
non-void; behavior unchanged (still only logs on persist failure).

NOTE (runtime dependency, for controlled deploy): this branch's chat.el calls
engram_get_node_by_label, which the canonical el-compiler/runtime does not yet
declare/define (the release runtime v1.0.0-20260501 has it; the newest runtime
has arena + http_serve_async but not this). Building the soul requires a runtime
that has all three. Land engram_get_node_by_label into the runtime package
before this soul.c can be built in CI.

Do not merge — regen + Track B going live is a controlled-deploy call.
2026-07-14 18:45:14 -05:00
Tim Lingo aa67f86f90 propose(agentic): narrated runs — live run-progress ledger + narration on the pause envelope
Neuron Soul CI / build (pull_request) Successful in 4m47s
Neuron Soul CI / deploy (pull_request) Has been skipped
The model already narrates its intent in a text block before every tool call;
agentic_loop DISCARDED that prose on tool rounds. Now: (1) each loop round
appends {i, t: narration, tool} to state key run_progress_<sid>, reset at run
start, closed with {done:true}; (2) new GET /api/run-progress/<sid> returns the
ledger so clients poll live step updates during a run (the Cowork pattern,
no streaming needed); (3) tool_pending envelope gains a narration field;
(4) handle_config display default aligned to the intended product default
(claude-sonnet-4-5 silently became fresh-profile pickers' default).

Compiled proof for the running test bed:
neuron-container-build/soul-narrated-runs-20260713.patch (applies on top of
soul-webfix-20260711.patch); E2E-verified live: ledger filled DURING an agentic
run (narration + tool per round), safety-contact and workspace scoping intact.

Evidence for why: Tim's 2026-07-13 research run — 9 minutes of silence, then a
timeout banner, zero step visibility (compounded by the Haiku 4.5 incident
14:44-15:24 UTC same morning).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 11:06:51 -05:00
Tim Lingo 01446e644b feat(agent): BUG-8 — server-side risk tiers + run_command workspace fence
Neuron Soul CI / build (pull_request) Successful in 5m7s
Neuron Soul CI / deploy (pull_request) Has been skipped
Enforcement moves from the client into the engine, where the tools execute:

- classify_tool_risk() tiers every tool call read/reversible/escalate. The
  agentic loop REFUSES to auto-run the escalate tier — being a builtin is no
  longer a free pass, and 'always allow' can never bypass escalate (irreversible
  actions always confirm, the value line). Escalate suspends to the client's
  existing consent bridge; the /approve round-trip is the only path that runs it.
  risk_tier rides the tool_pending envelope so the client renders consent weight.
- run_command_guard() is a real fence, not a cwd suggestion: refuses parent
  traversal, ~, command substitution, and absolute paths outside the workspace,
  and refuses shell entirely when no workspace is set. Applied in dispatch_tool
  so BOTH the loop auto-run and the post-consent approve-dispatch path are fenced.
- web_get gained an http(s)-only scheme guard (previously unguarded — file:// etc).

Adversarially verified against a compiled soul in an isolated container (soul
hit directly, app gate out of the loop): read-outside-workspace denied,
write-class shell suspends for consent, approve-swapped absolute/chaining/
command-substitution escapes all refused with no file created, file:// denied;
legit in-workspace approve executes and read commands auto-run (no over-block).

Still lexical (symlinks); OS-level confinement in el_runtime.c remains the
ceiling, flagged in the LIMITATION note. This closes BUG-8's client-only-gate
and escapable-run_command at the engine. dist/soul.c must be regenerated from
this chat.el via elb at merge (hand-port used only to verify behavior).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:45:52 -05:00
Tim Lingo 92f51885bc refactor(chat): local-toolchain compatibility — hoist affective block, de-shadow session_preload (zero behavior change)
Two mechanical refactors, semantics identical:
- affective_context_prefix(): the block-expression initializer form miscompiles
  under locally-buildable elc (first typed let in a block-expr loses its
  declaration — 3-line repro filed); function-hoist compiles correctly.
  AFFECTIVE/CARE LOGIC BODY UNCHANGED, verbatim move.
- session_preload: same-scope re-let shadowing inside an if-expression
  initializer emits duplicate C declarations; chained bindings renamed
  bullets_0/1/2 etc. References preserved binding-for-binding.

Enables: chat.el compiles cleanly with a self-bootstrapped elc from el/lang
main (Jul 1). Blocked separately: sessions.el (compiler hang), safety.el
(string-lexing corruption — NOT touched, per safety-layer discipline).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:35:59 -05:00
will.anderson 71bb0820ce Merge PR #65: soul: OpenAI-compatible provider path for chat (Ollama/OpenAI/Grok/Gemini) v1
Neuron Soul CI / build (push) Successful in 5m51s
Neuron Soul CI / deploy (push) Failing after 8m15s
Adds llm_base_url()/llm_wire_format() env-var readers and
openai_chat_complete() for basic (non-agentic) chat via any
OpenAI-compatible endpoint. Activated when NEURON_LLM_0_FORMAT=openai
and NEURON_LLM_0_URL is set; Anthropic path is untouched and remains
default. Agentic tool loop support deferred to a follow-up PR.
2026-07-01 11:35:02 -05:00
will.anderson d67f4c8f08 Merge PR #66: soul: inject current engine into system prompt for truthful self-report
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
Adds current_engine_note() to chat.el and appends it to the system
prompt in handle_chat. Allows Neuron to answer 'what model am I
running on?' accurately — the model id from the request body (or
the configured default) is passed as a factual annotation rather
than expecting the LLM to guess from training data.
2026-07-01 11:34:34 -05:00
will.anderson 31dd93d5f4 fix(chat): add distill_transcript (was called but never defined)
handle_dharma_room_turn and handle_dharma_chat both called
distill_transcript since June 30 but the function was never declared,
causing a build failure. Implements last-3-messages extraction for JSON
array transcripts and last-500-char truncation for plain text.
2026-07-01 11:25:48 -05:00
Tim Lingo b24f6d645b soul: let Neuron answer 'what model am I running on?' — inject current engine into system prompt
Neuron Soul CI / build (pull_request) Failing after 10m43s
Neuron Soul CI / deploy (pull_request) Has been skipped
Additive: appends a factual [CURRENT ENGINE: <model>] line to the system prompt (model from the
request body — accurate even under Auto routing; falls back to configured default). An LLM can't
know its own model from training (name/version assigned post-training), so the harness must tell it.
Identity-consistent: model = engine, self layered on top. Does NOT alter identity/values/safety.
PARSES (elc chat.el exit 0); NOT built/tested — ships with the soul rebuild.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 19:13:10 -05:00
Tim Lingo 39acb55d4f soul: OpenAI-compatible provider path for chat (Ollama/OpenAI/Grok/Gemini) — v1 basic completion
Neuron Soul CI / build (pull_request) Failing after 17m19s
Additive, Anthropic path untouched + default. When NEURON_LLM_0_FORMAT=openai and NEURON_LLM_0_URL
set, basic chat turns build an OpenAI chat/completions request and parse choices[0].message.content.
v1 = plain completion, NO tools/agentic loop yet (follow-up). Unblocks all OpenAI-format providers
at once. PARSES (elc chat.el exit 0); NOT yet built/tested — needs the soul rebuild (dist/soul.c) + E2E.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:52:26 -05:00
will.anderson 933547265e chore(dist): compile PRs #60/#61 into soul.c
Neuron Soul CI / build (push) Successful in 4m3s
Neuron Soul CI / deploy (push) Failing after 5m12s
- PR #60: inject operator home dir into system prompt (#30)
  Adds OPERATOR IDENTITY section so the LLM correctly resolves
  'my files/notes/desktop' to the actual running user's $HOME.
  Prevents identity confusion between imprint author and operator.

- PR #61: plan-mode endpoint POST /api/chat {mode:'plan'} (#27)
  Adds handle_chat_plan — returns {steps:[{id,title,detail}]} JSON.
  Wired into all three /api/chat route handlers. Grounds the plan
  via engram_compile (same as agentic path) for context awareness.

dist changes:
  - soul.c: both PRs compiled in; build_system_prompt updated to
    2-param signature (ctx, chat_mode); handle_chat_plan added
  - chat.c/routes.c/chat.elh: individual module outputs updated
  - elp-c-decls.h: remove stale 1-param build_system_prompt decl,
    add handle_chat_plan declaration
  - soul.elh.c: new soul header declarations file (from PR #60)

Compile verified: cc -O2 -DHAVE_CURL soul.c el_runtime.c -lcurl
Binary: 805K arm64, smoke test passes (port in use = expected).
2026-06-29 08:17:45 -05:00
Tim Lingo f47c92a71a feat: vision in the agentic chat path (image content block)
Neuron Soul CI / build (pull_request) Failing after 23m26s
handle_chat_agentic now reads body image + image_media_type and, when present, sends the current
user turn as an Anthropic content-block array [{text},{image}] instead of a plain string — so the
model sees raw pixels alongside memory, history, and tools (parity with the CLI). Additive: no image
=> output byte-identical to before. elc-clean. Pairs with neuron-ui fix/chat-vision-attachments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:25:26 -05:00
will.anderson 4a44c24bfb fix(recall): wire id_in_seen guards into session_preload node renders
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 7m29s
All 8 session_preload node accesses (3 profile, 2 work, 2 project, 1
summary) now check id_in_seen(node_id, seen_ids) before including
content. seen_ids is populated by engram_compile via state and covers
all nodes already in the activation+search context block. Prevents
high-salience nodes from appearing twice in the system prompt.
2026-06-22 15:08:30 -05:00
will.anderson f2b63f0048 fix(emergency): repair session-continuity regressions from prior merge 2026-06-22 14:51:51 -05:00
will.anderson 774688cfb9 fix/session-continuity-hook
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 6m0s
2026-06-22 14:29:31 -05:00
will.anderson aa2404b3f7 fix/context-dedup-shared-ids 2026-06-22 14:29:06 -05:00
will.anderson f73c913498 fix(session-continuity): address all adversarial review findings
Issue 1 (CRITICAL): Restore parse_float_x100 for correct single-decimal
float handling. "0.9" now correctly yields 90, not 9. Also restores
engram_numeric_valid guard that validates inputs before str_to_int.

Issue 2 (CRITICAL): Fix handle_chat_agentic safety screen history key
regression. state_get("conversation_history") -> state_get("conv_history")
so the safety screen receives actual history instead of always "".

Issue 3 (REAL BUG): Replace _sel_N JSON sentinel injection in
engram_compile_ranked with |N| index string tracking. Sentinels were
leaking into node JSON delivered to the LLM and cleanup only covered
indices 0-14, leaving indices 15+ uncleaned.

Issue 4 (REGRESSION): Restore rendered conversation history formatting.
Conversation history is now rendered as "User: .../Assistant: ..." with
400-char truncation per turn, not raw JSON array injection.

Issue 5 (SCOPE/SAFETY): Restore removed defensive code: engram_numeric_valid
and parse_float_x100 guards; conv_history_load label-based fetch + partial-
write guard + load-failure state flag; conv_history_persist partial-write
guard + failure logging; hist_warning in response envelope.

Issue 6 (UNDOCUMENTED): Restore bell event cutoff from 259200s (3 days)
back to 1209600s (14 days). Also restore PositiveEvent affective context
search that was removed alongside the cutoff change.

Issue 7 (LOGIC REGRESSION): Fix affective_prefix to run every turn
(not just hist_len == 0). The care/joy directives must persist throughout
the session, not vanish after turn 1.

Issue 8 (MINOR): session_summary_write_dated now uses el_from_float(0.85)
for salience and importance (two-decimal) to avoid any ambiguity in float
parsing, and the function is re-added with the session-end hook.
2026-06-22 14:25:29 -05:00
will.anderson 588ca11f57 fix(context-dedup): include scan_part and affective_part IDs in seen set
Two design bugs in the state_set placement caused the dedup seen-ID set
to be incomplete even with callsites wired up:

1. state_set("engram_compile_seen_ids") was called immediately after
   merging the main node pools, before scan_part (persona fallback) and
   affective_part (bell node) were computed. Nodes appearing only in
   those segments were never added to the seen set.

2. affective_part is a bare JSON object (bn0 from json_array_get), not
   a JSON array. Passing it to engram_extract_ids would have gotten
   json_array_len == 0 and silently skipped the affective node's ID.

Fix: move state_set to after ctx is assembled from all three segments.
Extract ids_from_merged and ids_from_scan via engram_extract_ids (both
are JSON arrays), and extract ids_from_affective via json_get(affective_part, "id")
directly since it is a bare object. Merge all three via add_to_seen
before publishing to state.
2026-06-22 14:19:14 -05:00
will.anderson 9e178d8371 fix(recall): deduplicate engram nodes by ID across activation and search passes
Thread a seen-node-ID exclusion set from engram_compile() through to
session_preload in handle_chat, preventing the same high-salience nodes
(identity, recent memories) from appearing 2-3x in the system prompt.

Changes:
- Add id_in_seen(), add_to_seen(), engram_extract_ids() helpers that
  maintain a comma-delimited seen-ID accumulator (EL has no Set type)
- In engram_compile(): after merging all topic/entity/recall pools, extract
  node IDs from merged_nodes and publish via state_set(engram_compile_seen_ids)
- In handle_chat(): read seen_ids from state after engram_compile() returns,
  then check id_in_seen() before emitting each session_preload bullet
  (profile x3, work x2, project x2, summary x1 — all 8 candidate nodes guarded)

Nodes already present in the compiled engram context are skipped in preload,
eliminating 3000-3500 token repetition on first-message turns.
2026-06-22 14:06:04 -05:00
will.anderson aaada3770a fix(recall): deduplicate engram nodes by ID across activation and search passes
engram_compile() already published seen node IDs to state via engram_compile_seen_ids
but handle_chat never read or applied them. Wire up the consumption side:

- Read engram_compile_seen_ids from state after engram_compile() returns
- Check each session_preload candidate node (profile x3, work x2, project x2,
  summary x3) against id_in_seen() before emitting its content bullet
- Nodes already present in the compiled engram context are skipped entirely,
  preventing the same high-salience identity/memory nodes from appearing 2-3x
  in the system prompt and burning 3000-3500 tokens on repetition
2026-06-22 14:03:48 -05:00
will.anderson a0299c0a89 fix(recall): session-end summary hook + session summary recall at start 2026-06-22 14:01:56 -05:00
will.anderson 33cb1138f4 fix(recall): set threshold=25 in all engram_compile_ranked variants 2026-06-22 13:58:17 -05:00
will.anderson ec7efdeeb7 fix(recall): engram score float parsing — pad to 2 decimals before strip 2026-06-22 13:57:33 -05:00
will.anderson c93be6a315 feat(recall): context-format
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 13m54s
2026-06-22 13:29:12 -05:00
will.anderson 53268c94b9 feat(recall): activation-seed 2026-06-22 13:29:12 -05:00
will.anderson 7e43a4ddc0 feat(recall): context-dedup 2026-06-22 13:29:12 -05:00
will.anderson e7669da325 feat(recall): session-start-recall 2026-06-22 13:29:12 -05:00
will.anderson 4f1286df05 feat(recall): cross-session-continuity 2026-06-22 13:29:12 -05:00
will.anderson 52c222c4f2 feat(recall): engram-scoring 2026-06-22 13:29:12 -05:00
will.anderson 0caccd0ea5 feat(recall): temporal-precision 2026-06-22 13:29:12 -05:00
will.anderson 03b5632fc1 feat(recall): recall-reliability 2026-06-22 13:29:12 -05:00
will.anderson 42bbadcd33 Merge pull request 'feat(recall): emotional-recall improvements' (#52) from improve/recall-emotional-recall into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 5m49s
feat(recall): emotional-recall improvements
2026-06-22 18:24:36 +00:00
will.anderson 1dd09b1980 feat(recall): context-format improvements
Neuron Soul CI / build (pull_request) Has been cancelled
- Add engram_render_node/render_nodes/dedup_nodes helpers for human-readable
  prose bullet output instead of raw JSON node objects reaching the LLM
- Fix engram_compile_ranked to use |N| index sentinel instead of _sel_N JSON
  mutation which leaked sentinel fields into LLM-visible node data (Issue #11)
- Update build_system_prompt with chat_mode param; no_tools_rule only included
  for chat path, not agentic paths (Issue #9)
- Move engram block to end of system prompt for strongest LLM attention (Issue #8)
- Label sections: STABLE IDENTITY vs RETRIEVED MEMORY (Issue #10)
- Render conversation history as User:/Assistant: dialogue instead of raw JSON
- Add RETRIEVED MEMORY labels to agentic and dharma room system prompt assembly
2026-06-22 13:20:19 -05:00
will.anderson 0113407728 feat(recall): emotional-recall improvements
Neuron Soul CI / build (pull_request) Has been cancelled
2026-06-22 13:17:12 -05:00
will.anderson cbe8c09068 feat(recall): context-dedup improvements
Neuron Soul CI / build (pull_request) Has been cancelled
- Cache bell node in engram_compile state (engram_compile_bell_node)
  so handle_chat reads cached value instead of duplicate bell query (Issue 2)
- Cache activation result (engram_compile_activation_json) for strengthen_chat_nodes
  reuse — eliminates third activation query per turn (Issue 7)
- Fix context cap to truncate at clean JSON object boundary (Issue 6)
2026-06-22 13:15:33 -05:00
will.anderson dfa2a33926 feat(recall): context-dedup improvements
- Cache bell node result in engram_compile state (engram_compile_bell_node)
  so handle_chat affective_prefix reads the cached value instead of firing
  a duplicate engram query for distress signals (Issue 2)

- Cache primary activation result in engram_compile state
  (engram_compile_activation_json) using nodes0 from engram_compile_multi

- Replace redundant engram_activate_json(message, 2) in strengthen_chat_nodes
  with state_get(engram_compile_activation_json) - eliminates a third
  activation query per turn (Issue 7)

- engram_compile already has object-boundary truncation and cross-set
  dedup via engram_nodes_merge/engram_dedup_nodes (Issues 1, 6, 9)
2026-06-22 13:12:08 -05:00
will.anderson 3f53b6b1b6 feat(recall): session-start-recall improvements
Neuron Soul CI / build (pull_request) Has been cancelled
10 targeted fixes for session-start memory recall quality:

Issue 1: typed engram queries (Persona, WorkItem) replace generic keyword bags
Issue 2: bullet truncation raised from 120 to 350 chars
Issue 3: bullet caps raised to 8/6 with while-loop (no hardcoded unrolling)
Issue 4: read pre-computed soul_affective_context state key instead of duplicating boot-time search
Issue 5: last-session-topic node written per session; continuity section added to session_preload
Issue 6: greeting detection injects SESSION START orientation directive when continuity found
Issue 7: pinned identity node fallback when all engram searches return empty
Issue 8: session_preload always fires on first message (greeting detection controls directive only)
Issue 9: agentic path gets matching session_preload block (was missing entirely)
Issue 10: BellEvent recency reads created_at / embedded ts marker, not the never-written "ts" field
2026-06-22 13:06:55 -05:00
will.anderson 21f248a33a feat(recall): recall-completeness improvements
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
- Lower engram_compile_ranked threshold 25->15: include moderately-relevant older nodes
- Extend sentinel cleanup from _sel_9 to _sel_14 to prevent JSON noise
- Add engram_split_topics for multi-topic decomposition (AND/and/also/plus)
- Add engram_extract_entities for named entity dedicated searches
- Add engram_detect_recall_intent for boosted 40-candidate search on recall phrases
- Add engram_is_continuation replacing brittle 50-char threshold (now 80 + pronoun/opener detection)
- Add engram_compile_multi with depth 8 (was 5) and 30-candidate search pool
- Add engram_nodes_merge for clean two-array deduplication
- Replace engram_compile with multi-topic/entity/recall-boost version; budget 6000->8000
- Safe JSON truncation: scan for last } before budget cap instead of raw str_slice
- handle_chat and agentic_chat: use engram_is_continuation; thread snip 150->250
- session_preload: add project-status and session-summary search queries
2026-06-22 13:05:28 -05:00
will.anderson 795b32ad1a feat(recall): cross-session-continuity improvements
Neuron Soul CI / build (pull_request) Failing after 14m49s
2026-06-22 13:00:17 -05:00
will.anderson f33cdaf793 feat(recall): activation-seed improvements
- Issue 2: replace raw 50-char threshold with is_genuine_continuation() that
  checks for explicit follow-up phrases and mid-sentence capitalization (proper
  nouns signal a new topic, not a continuation)
- Issue 3/8: build_activation_seed() scans back to find the prior USER turn as
  the topic anchor instead of using the last assistant reply (hist_len-1)
- Issue 4: engram_compile_multi() fans out across three seeds — enriched primary,
  raw message (entity queries), and emotion query — merging non-redundant results
- Issue 5: agent workspace_root appended to ag_seed so agentic activation is
  workspace-aware; previously ignored despite being available in state
- Issue 6: distill_transcript() extracts salient tail+question content from full
  transcripts before passing to engram_compile in dharma room handlers
- Issue 7: dist/soul-with-nlg.el handle_chat and handle_chat_agentic now load
  history and use build_activation_seed() — the raw message path is eliminated
- Issue 9: topic_snip_from_entry() takes the TAIL 200 chars of a long reply and
  finds the last sentence boundary — captures end-of-reply named concepts
- Issue 10: multi_turn_topic() pulls up to 3 prior user turns into the non-
  continuation seed so earlier thread context re-activates high-salience nodes
2026-06-22 12:55:33 -05:00