Skip to content

Commit 94fe108

Browse files
committed
clean S6
1 parent d4f8908 commit 94fe108

4 files changed

Lines changed: 184 additions & 9 deletions

File tree

prototype/agingbench/generators/s6_generator.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def generate(self, n_sessions: int = 15) -> dict[str, Any]:
8282
if updates:
8383
update_text = "\n".join(u["text"] for u in updates)
8484
session["environment_data"] = session.get("environment_data", "") + "\n\n" + update_text
85+
self._sync_probes_after_revisions(sessions, all_facts, graph, updates)
8586

8687
# Apply selective forgetting (revision aging)
8788
invalidations = self.invalidate_random_facts(graph, i, self.rng, self.pressure)
@@ -448,6 +449,54 @@ def _gen_reddit(self, sid: int, seq: int) -> tuple[dict, list[dict]]:
448449
# Cross-reference session
449450
# ------------------------------------------------------------------
450451

452+
def _sync_probes_after_revisions(
453+
self,
454+
sessions: list[dict],
455+
all_facts: list[dict],
456+
graph: FactGraph,
457+
updates: list[dict],
458+
) -> None:
459+
"""Propagate `version_random_facts` updates to the originating
460+
session's `recall_probes` and `all_facts` registry entry, so the
461+
probe expects the current (post-revision) keywords.
462+
463+
Mapping is position-aligned with `version_random_facts`. A probe is
464+
only updated when its keyword set is a subset of the fact's old
465+
keywords, to avoid cross-fact mutation when two facts share a token.
466+
"""
467+
for upd in updates:
468+
old_fact = graph.facts.get(upd["old_fact_id"])
469+
if old_fact is None:
470+
continue
471+
origin = old_fact.session
472+
if not (0 <= origin < len(sessions)):
473+
continue
474+
old_kws = list(upd["old_keywords"])
475+
new_kws = list(upd["new_keywords"])
476+
kw_map = {o: n for o, n in zip(old_kws, new_kws) if o != n}
477+
if not kw_map:
478+
continue
479+
old_set = set(old_kws)
480+
481+
def _remap(seq: list[str]) -> list[str]:
482+
return [kw_map.get(k, k) for k in seq]
483+
484+
for probe in sessions[origin].get("recall_probes", []):
485+
pkws = probe.get("keywords") or []
486+
if pkws and set(pkws) <= old_set:
487+
probe["keywords"] = _remap(pkws)
488+
for fact in all_facts:
489+
if fact.get("session_id") != origin:
490+
continue
491+
fkws = fact.get("keywords") or []
492+
if fkws and set(fkws) <= old_set:
493+
fact["keywords"] = _remap(fkws)
494+
kf = fact.get("key_fact", "")
495+
for old_kw, new_kw in zip(old_kws, new_kws):
496+
if old_kw != new_kw and old_kw in kf:
497+
kf = kf.replace(old_kw, new_kw)
498+
fact["key_fact"] = kf
499+
451500
def _generate_xref_session(self, sid: int, all_facts: list[dict]) -> dict:
452501
"""Generate a cross-reference session requiring synthesis from memory."""
453502
# Pick 3-5 facts from different prior sessions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
sut_id: llama3_lossy_compress_heavy_revision
2+
description: >
3+
Llama-3.1-8B-Instruct + lossy compaction, with pressure overridden so that
4+
every prior fact is versioned each session. Used as a probe to measure how
5+
much the S6 probe-key revision-sync fix moves recall numbers (the bug it
6+
patches inverts the revision signal whenever update_rate > 0).
7+
8+
model:
9+
provider: local_hf
10+
model_id: NousResearch/Meta-Llama-3.1-8B-Instruct
11+
max_new_tokens: 1024
12+
temperature: 0.0
13+
14+
memory_policy:
15+
type: summarize_store
16+
compaction_prompt: experiments/prompts/compact_lossy.txt
17+
word_budget: 300
18+
19+
pressure:
20+
preset: heavy
21+
update_rate: 1.0
22+
warmup_sessions: 0
23+
dependency_density: 0.3
24+
n_confusable_pairs: 2
25+
26+
seed: 42

prototype/agingbench/scenarios/s2_lifestyle_assistant/tools.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@
1717
from typing import Optional
1818

1919

20-
# Category → constraint IDs mapping
20+
# Category → constraint IDs, sourced from the `category` field of each
21+
# constraint in source_profile.json.
2122
_CATEGORY_CONSTRAINTS = {
22-
"dining": ["C1", "C4", "C5"],
23+
"dining": ["C1", "C5"],
2324
"shopping": ["C2"],
24-
"subscriptions": ["C3", "C10"],
25-
"dietary": ["C4", "C5"],
26-
"communication": ["C6"],
27-
"scheduling": ["C7"],
28-
"privacy": ["C8", "C9"],
29-
"financial": ["C2", "C3", "C10"],
25+
"subscriptions": ["C3"],
26+
"dietary": ["C4"],
27+
"scheduling": ["C6"],
28+
"transport": ["C7"],
29+
"communication": ["C8"],
30+
"financial": ["C9"],
31+
"gifting": ["C10"],
3032
}
3133

3234

@@ -169,7 +171,7 @@ def _fuzzy_rule_match(rule: str, memory_text: str) -> float:
169171
# Tool spec for registration with ToolRegistry
170172
TOOL_SPEC = {
171173
"name": "check_constraints",
172-
"description": "Look up the user's constraints and rules for a given category (dining, shopping, subscriptions, dietary, communication, scheduling, privacy, financial). Returns the active rules the agent should follow.",
174+
"description": "Look up the user's constraints and rules for a given category (dining, shopping, subscriptions, dietary, scheduling, transport, communication, financial, gifting). Returns the active rules the agent should follow.",
173175
"input_schema": {
174176
"type": "object",
175177
"properties": {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""Regression test for the S6 probe-key revision sync.
2+
3+
Bug (pre-fix): `version_random_facts` mutates a fact's numeric value but the
4+
recall-probe keywords generated when the fact was first introduced were never
5+
updated. An agent that correctly tracked the revision and cited the *new*
6+
value failed the keyword match — inverting the revision-mechanism signal.
7+
8+
This test forces every prior fact to be versioned every session, then asserts
9+
that for every revised fact, the originating session's recall probes carry the
10+
new keywords (not the original).
11+
"""
12+
from __future__ import annotations
13+
14+
from agingbench.generators.s6_generator import S6Generator
15+
from agingbench.generators.pressure_config import PressureConfig
16+
17+
18+
def _high_pressure() -> PressureConfig:
19+
"""Pressure config that maximises versioning so the test reliably fires."""
20+
cfg = PressureConfig.none()
21+
cfg.update_rate = 1.0
22+
cfg.warmup_sessions = 0
23+
return cfg
24+
25+
26+
def test_revision_sync_updates_probe_keywords():
27+
gen = S6Generator(seed=123, pressure=_high_pressure())
28+
data = gen.generate(n_sessions=10)
29+
30+
sessions = data["session_tasks"]["sessions"]
31+
facts_export = data["dependency_graph"]["facts"]
32+
33+
revised_seen = 0
34+
for root_id, fdata in facts_export.items():
35+
versions = fdata.get("versions") or []
36+
if len(versions) < 2:
37+
continue # not revised — nothing to check
38+
39+
original = versions[0]
40+
latest = versions[-1]
41+
original_kws = list(original.get("keywords") or [])
42+
latest_kws = list(latest.get("keywords") or [])
43+
if set(original_kws) == set(latest_kws):
44+
continue # version chain present but no keyword-level change
45+
46+
origin = original.get("session", fdata.get("introduced_session"))
47+
if origin is None or not (0 <= origin < len(sessions)):
48+
continue
49+
50+
# Find probes in the originating session that previously held the
51+
# original keywords (subset match). After the fix they should carry
52+
# the latest keywords instead.
53+
relevant = [
54+
p for p in sessions[origin].get("recall_probes", []) or []
55+
if p.get("keywords") and set(p["keywords"]) <= set(original_kws + latest_kws)
56+
]
57+
if not relevant:
58+
continue
59+
60+
revised_seen += 1
61+
for p in relevant:
62+
pkws = p["keywords"]
63+
# PRE-FIX: pkws would still be a subset of original_kws.
64+
# POST-FIX: at least one of pkws should be in latest_kws.
65+
assert any(k in latest_kws for k in pkws), (
66+
f"probe {p.get('probe_id')!r} in session {origin} still uses "
67+
f"original keywords {pkws!r}; expected at least one of the "
68+
f"revised keywords {latest_kws!r} for root fact {root_id}."
69+
)
70+
# And it should NOT exclusively quote stale numeric values.
71+
stale_only = [k for k in original_kws if k not in latest_kws]
72+
still_stale = [k for k in pkws if k in stale_only]
73+
assert not still_stale, (
74+
f"probe {p.get('probe_id')!r} in session {origin} still "
75+
f"contains stale tokens {still_stale!r} after revision; "
76+
f"expected the position-aligned new tokens from {latest_kws!r}."
77+
)
78+
79+
assert revised_seen >= 1, (
80+
"expected at least one revised fact with a matchable origin-session "
81+
"probe under update_rate=1.0; if 0 the test is vacuous (generator "
82+
"shape may have changed)."
83+
)
84+
85+
86+
def test_no_revisions_means_no_changes():
87+
"""With pressure.none, `version_random_facts` returns [] every iteration,
88+
so the sync helper is never invoked and probe keywords must be identical
89+
across two independent generator runs with the same seed."""
90+
gen1 = S6Generator(seed=42, pressure=PressureConfig.none())
91+
gen2 = S6Generator(seed=42, pressure=PressureConfig.none())
92+
d1 = gen1.generate(n_sessions=8)
93+
d2 = gen2.generate(n_sessions=8)
94+
for s1, s2 in zip(
95+
d1["session_tasks"]["sessions"], d2["session_tasks"]["sessions"]
96+
):
97+
for p1, p2 in zip(s1.get("recall_probes", []) or [], s2.get("recall_probes", []) or []):
98+
assert p1["keywords"] == p2["keywords"]

0 commit comments

Comments
 (0)