fix(nac): canonicalize tool signatures + substring-scan keyword query#222
Merged
fix(nac): canonicalize tool signatures + substring-scan keyword query#222
Conversation
…uery
The post-merge sim audit surfaced two related bugs that nullified the
"learned causal predictions in prompt" feature for every sim.
Phase 1 — Storage canonicalization
Three NAc storage formats coexisted for the same logical tool:
- tool_dispatch / tool_pain_bridge: "tool:<name>" (canonical)
- planning_bridge: "<name>" (bare, drift)
- exec_agent / agent_pool: their own deliberate non-tool formats
Persisted aut_nac.json showed both forms in _links for every tool that
took a plan_outcome path:
['tool:rusty_sword_slash', 'rusty_sword_slash', ...]
Same logical tool, two dict keys, queries hit at most one of them.
planning_bridge.py:369 (observe) and :272 (predict) now route through
build_tool_signature, the function tool_dispatch.py documents as "the
single source of truth for tool→NAc event signature format".
memory_agent.py:1414's _build_causal_context query also migrated, so
the AUT's learned-causal-context section finds the same links the
runtime wrote.
Phase 1.5 — Substring scan in bio_enrichment._query_nac
NAc stores compound signatures like "tool:rusty_sword_slash" but
bio_enrichment extracts narrative keywords like "rusty", "sword",
"slash" from percept text and queried get_links_for_event(kw)
(exact dict lookup). Never matched anything across all three pre-fix
sims (predictions=0 in every enrichment_trace).
Added NAc.scan_links_for_keywords as a public companion to
get_links_for_event: case-insensitive substring containment, dedupes
by link id, sorts by confidence, drops short stop-words. Encapsulates
the _links scan so callers don't poke private state.
bio_enrichment._query_nac now delegates to it; the legacy raw-keyword
path is gone. Verification sim (qwen2.5-14b-instruct via leader, 5
turns embodied) shows predictions=1 in 2 of 4 enrichment events
(zero in every pre-fix run) and a clean canonical _links shape.
Out of scope, deferred:
- _reward_bias is empty in tool-only sims by design — distribute_reward
fires only on Reaction events (pain/valence). Phase 2/3 substrate
bridge waits on a damage-taking sim verifying reward_bias actually
populates first.
- _query_atl exact-match (concepts=0) has the same shape as the bug
fixed here; same fix would apply but not blocking the prompt
predictions path.
- agent_pool.py "{agent_id}:respond" left as documented different
format (no other writer or reader uses build_tool_signature shape
for NPC turn outcomes).
Tests: +9 (4 scan_links_for_keywords cases + 1 canonical-signature
regression in test_memory_hub + 4 existing tests updated to mock the
new method instead of the old one).
Total: 6336 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Fixes the predictions=0 in every enrichment_trace finding from the post-#218 sim audit. Two related bugs:
Phase 1 — Storage canonicalization. Three NAc storage formats coexisted for the same logical tool:
tool_dispatch.py/tool_pain_bridge.pystoredtool:<name>(canonical, viabuild_tool_signature)planning_bridge.pystored<name>(bare, drift)Persisted
aut_nac.jsonfrom a verification sim showed both forms in_linksfor every tool that ever took a plan-outcome path:Same logical tool, two dict keys, queries hit at most one.
planning_bridge(observe + predict) andmemory_agent._build_causal_context(query) all migrated to route throughbuild_tool_signature, which is documented as "the single source of truth for tool→NAc event signature format."Phase 1.5 — Substring scan in
bio_enrichment._query_nac. NAc stores compound signatures liketool:rusty_sword_slash, butbio_enrichmentextracts narrative keywords likerusty,sword,slashfrom percept text and calledget_links_for_event(kw)— an exact dict lookup that never matched anything across the three pre-fix sims (predictions=0in everyenrichment_trace).Added
NAc.scan_links_for_keywordsas a public companion toget_links_for_event: case-insensitive substring containment, dedupes by link id, sorts by confidence, drops short stop-words belowmin_keyword_length.bio_enrichment._query_nacnow delegates to it.Verification
A 5-turn embodied sim (
qwen2.5-14b-instructvia leader) before vs after this branch:_linkskeys['tool:rusty_sword_slash', 'rusty_sword_slash', ...](dual-form)['tool:rusty_sword_slash', 'tool:sense_tools'](canonical only)enrichment_trace.predictionsOut of scope, deferred
_links_by_nodebridge) is paused. Substrate state inspection across 4 sims showed_reward_biasis fully empty in tool-only sims by design —distribute_rewardonly fires onReactionevents (pain / valence), which don't trigger from plain tool dispatches. Bridging_linksto substrate node_ids would expose data that doesn't exist for typical sims. Revisit when a damage-taking combat sim verifies_reward_biaspopulates somewhere first._query_atl(concepts=0) has the same exact-match shape as the bug fixed here; same fix pattern would apply but it's not blocking the predictions path.agent_pool.py "{agent_id}:respond"is a deliberately different format for NPC turn outcomes; no other writer or reader usesbuild_tool_signatureshape for it. Documented in place.Test plan
ruff check+ruff formaton every touched filescan_links_for_keywordscases (substring match / dedupe-and-sort / short-keyword drop / confidence floor)record_plan_outcome_uses_canonical_signatureregression in test_memory_hubtest_bio_enrichmenttests updated to mock the new query methodpredictions > 0and clean_linksshape🤖 Generated with Claude Code