feat: length-aware reinforcement + batch triage CLI + off-host backup

- Reinforcement matcher now handles paragraph-length memories via a
  dual-mode threshold: short memories keep the 70% overlap rule,
  long memories (>15 stems) require 12 absolute overlaps AND 35%
  fraction so organic paraphrase can still reinforce. Diagnosis:
  every active memory stayed at reference_count=0 because 40-token
  project summaries never hit 70% overlap on real responses.
- scripts/atocore_client.py gains batch-extract (fan out
  /interactions/{id}/extract over recent interactions) and triage
  (interactive promote/reject walker for the candidate queue),
  matching the Phase 9 reflection-loop review flow without pulling
  extraction into the capture hot path.
- deploy/dalidou/cron-backup.sh adds an optional off-host rsync step
  gated on ATOCORE_BACKUP_RSYNC, fail-open when the target is offline
  so a laptop being off at 03:00 UTC never reds the local backup.
- docs/next-steps.md records the retrieval-quality sweep: project
  state surfaces, chunks are on-topic but broad, active memories
  never reach the pack (reflection loop has no retrieval outlet yet).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 11:20:03 -04:00
parent c5bad996a7
commit 9366ba7879
5 changed files with 294 additions and 6 deletions

View File

@@ -476,6 +476,60 @@ def test_reinforce_matches_at_70_percent_threshold(tmp_data_dir):
assert any(r.memory_id == mem.id for r in results)
def test_reinforce_long_memory_matches_on_absolute_overlap(tmp_data_dir):
"""A paragraph-length memory should reinforce when the response
echoes a substantive subset of its distinctive tokens, even though
the overlap fraction stays well under 70%."""
init_db()
mem = create_memory(
memory_type="project",
content=(
"Interferometer architecture: a folded-beam configuration with a "
"fixed horizontal interferometer, a forty-five degree fold mirror, "
"a six-DOF CGH stage, and the mirror on its own tilting platform. "
"The fold mirror redirects the beam while the CGH shapes the wavefront."
),
project="p05-interferometer",
confidence=0.5,
)
interaction = _make_interaction(
project="p05-interferometer",
response=(
"For the interferometer we keep the folded-beam layout: horizontal "
"interferometer, fold mirror at forty-five degrees, CGH stage with "
"six DOF, and the mirror sitting on its tilting platform. The fold "
"mirror redirects the beam and the CGH shapes the wavefront."
),
)
results = reinforce_from_interaction(interaction)
assert any(r.memory_id == mem.id for r in results)
def test_reinforce_long_memory_rejects_thin_overlap(tmp_data_dir):
"""Long memory + a response that only brushes a few generic terms
must NOT reinforce — otherwise the reflection loop rots."""
init_db()
mem = create_memory(
memory_type="project",
content=(
"Polisher control system executes approved controller jobs, "
"enforces state transitions and interlocks, supports pause "
"resume and abort, and records auditable run logs while "
"never reinterpreting metrology or inventing new strategies."
),
project="p06-polisher",
confidence=0.5,
)
interaction = _make_interaction(
project="p06-polisher",
response=(
"I updated the polisher docs and fixed a typo in the run logs section."
),
)
results = reinforce_from_interaction(interaction)
assert all(r.memory_id != mem.id for r in results)
def test_reinforce_rejects_below_70_percent(tmp_data_dir):
"""Only 6 of 10 content tokens present (60%) → should NOT match."""
init_db()