Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .agent/harness/hooks/_episodic_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ def episodic_lock(path: str, *, exclusive: bool = True) -> Iterator[None]:
)


def is_legacy_episodic_row(entry: dict) -> bool:
"""True for pre-canonical rows that used date/summary instead of timestamp/action.

Replay and dream clustering must skip these — they lack evidence_ids and the
fields downstream loaders expect. Canonical re-encodes live as normal rows.
"""
if not isinstance(entry, dict):
return False
return (
"date" in entry
and "summary" in entry
and "timestamp" not in entry
and "action" not in entry
)


def append_jsonl(path: str, entry: dict) -> dict:
"""Append one UTF-8 JSON line while excluding concurrent rewrites/appends."""
payload = (json.dumps(entry) + "\n").encode("utf-8")
Expand Down
7 changes: 5 additions & 2 deletions .agent/memory/auto_dream.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
HARNESS_HOOKS = os.path.join(AGENT_ROOT, "harness", "hooks")
if HARNESS_HOOKS not in sys.path:
sys.path.insert(0, HARNESS_HOOKS)
from _episodic_io import episodic_lock # noqa: E402
from _episodic_io import episodic_lock, is_legacy_episodic_row # noqa: E402
from path_hygiene import sanitize_json_strings # noqa: E402

EPISODIC = os.path.join(ROOT, "episodic/AGENT_LEARNINGS.jsonl")
Expand Down Expand Up @@ -63,9 +63,12 @@ def _load_entries_locked(_fd):
if not line:
continue
try:
entries.append(json.loads(line))
entry = json.loads(line)
except json.JSONDecodeError:
continue
if is_legacy_episodic_row(entry):
continue
entries.append(entry)
Comment on lines +66 to +71

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 6 \
  'is_legacy_episodic_row|if not entries|_write_entries_locked' \
  .agent/memory .agent/harness/hooks

Repository: diazMelgarejo/Perpetua-Tools

Length of output: 6507


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== auto_dream.py relevant sections =="
sed -n '1,130p' .agent/memory/auto_dream.py
sed -n '185,215p' .agent/memory/auto_dream.py
echo
echo "== _episodic_io.py relevant sections =="
sed -n '70,120p' .agent/harness/hooks/_episodic_io.py
echo
echo "== decay_old_entries usages/usages with archived semantics =="
rg -n -C 4 'decay_old_entries|archived|snapshot|snapshot' .agent/memory .agent/harness/hooks

Repository: diazMelgarejo/Perpetua-Tools

Length of output: 50385


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== target file sizes =="
wc -l .agent/memory/auto_dream.py .agent/harness/hooks/_episodic_io.py
echo
echo "== focused target slices =="
sed -n '50,215p' .agent/memory/auto_dream.py
printf '\n---\n'
sed -n '75,180p' .agent/harness/hooks/_episodic_io.py
echo
echo "== exact target identifiers =="
rg -n -C 3 '^def (is_legacy_episodic_row|_load_entries_locked|_write_entries_locked|decay_old_entries)|def (cluster_and_extract|write_candidates|write_review_queue_summary)|ARCHIVE|epoch|timestamp|date|summary|max_age|threshold|rfind|split\(|\.lstrip|with open|os\.replace|JSONDecodeError|FileNotFoundError' .agent/memory/auto_dream.py .agent/harness/hooks/_episodic_io.py
echo
echo "== related tests (if any) =="
rg -n -C 3 'auto_dream|legacy_episodic|AGENT_LEARNINGS|decay_old_entries' tests .pytest.ini pyproject.toml 2>/dev/null || true

Repository: diazMelgarejo/Perpetua-Tools

Length of output: 26421


Document or preserve filtered legacy episodes.

_load_entries_locked() skips date/summary rows when canonical rows exist, then run_dream_cycle() writes only kept back to AGENT_LEARNINGS.jsonl. This silently removes those legacy rows from the persistent store unless the file contains only legacy rows. If filtering is replay-only, archive them independently; otherwise add a test/docs note documenting the cleanup policy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.agent/memory/auto_dream.py around lines 66 - 71, The _load_entries_locked()
flow filters legacy episodic rows before run_dream_cycle() rewrites
AGENT_LEARNINGS.jsonl, potentially deleting them silently. Preserve filtered
legacy rows independently during replay, or explicitly document and test that
this rewrite intentionally cleans them up; ensure the chosen policy is
implemented consistently around is_legacy_episodic_row() and the kept-entry
write path.

except FileNotFoundError:
pass
return entries
Expand Down
42 changes: 42 additions & 0 deletions .agent/memory/candidates/graduated/2d53056e593c.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"id": "2d53056e593c",
"key": "manual_2d5305",
"name": "manual_2d5305",
"claim": "Several tooling and API gotchas recur across a working session even after being individually caught once, because a single encounter doesn't automatically generalize into a standing rule: the PR-list API's merged field being unreliable (must check the single-PR endpoint); squash-merged branches never showing as git ancestors (verify by content/ID presence, not ancestry); a stale local checkout after an earlier push in the same session diverging silently; and diff-scoped CI lint checking a whole touched file, not just the changed hunk. Each of these needs to be treated as a standing checklist item to consult before the relevant operation, not a one-off lesson learned and then re-derived from scratch the next time it's encountered.",
"conditions": [
"pr-list-merged-field",
"squash-merge-ancestry",
"diff-scoped-lint",
"stale-checkout",
"recurring-gotcha"
],
"evidence_ids": [
"2026-08-01T06:53:43.671436+00:00"
],
"cluster_size": 1,
"canonical_salience": 8.0,
"staged_at": "2026-08-01T06:53:43.671436+00:00",
"status": "accepted",
"decisions": [
{
"ts": "2026-08-01T06:53:43.671436+00:00",
"action": "staged",
"reviewer": "learn"
},
{
"ts": "2026-08-01T06:53:50.650448+00:00",
"action": "graduated",
"reviewer": "host-agent",
"notes": "Several gotchas recurred even after individual capture -- worth the meta-lesson that a checklist consulted before the operation, not recall alone, is what actually prevents repetition.",
"provisional": false,
"evidence_snapshot": [
"2026-08-01T06:53:43.671436+00:00"
],
"lessons_sha": "4bce83c74754"
}
],
"rejection_count": 0,
"accepted_at": "2026-08-01T06:53:50.650434+00:00",
"reviewer": "host-agent",
"rationale": "Several gotchas recurred even after individual capture -- worth the meta-lesson that a checklist consulted before the operation, not recall alone, is what actually prevents repetition."
}
42 changes: 42 additions & 0 deletions .agent/memory/candidates/graduated/718c9430f44b.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"id": "718c9430f44b",
"key": "manual_718c94",
"name": "manual_718c94",
"claim": "When resolving real overlap between a new draft and an existing, more comprehensive doctrine, \"avoid duplication\" does not automatically mean \"make the newer, simpler thing subordinate to the older, more complex one.\" It means determining which document actually serves which situation and sizing each to its own job. A heavyweight protocol built for a rare, hard problem (e.g. concurrent multi-agent edits to the same repo, or reconciling a fork separated from its source by months of drift) should not become the default an agent reads first for the common, simple case (one branch, one agent, no concurrent editing) just because it existed first and is more thorough. Getting this backwards is not caught by re-reading a project's own stated single-source-of-truth value, since that value is genuinely being honored in some sense (no literal duplication) even while the sizing is wrong -- it may require direct correction from a human collaborator who can see the actual audience mismatch.",
"conditions": [
"doctrine-overlap",
"thin-addendum",
"subordinate-doc",
"single-source-of-truth",
"worktree-merge-card"
],
"evidence_ids": [
"2026-08-01T06:53:43.608740+00:00"
],
"cluster_size": 1,
"canonical_salience": 8.0,
"staged_at": "2026-08-01T06:53:43.608740+00:00",
"status": "accepted",
"decisions": [
{
"ts": "2026-08-01T06:53:43.608740+00:00",
"action": "staged",
"reviewer": "learn"
},
{
"ts": "2026-08-01T06:53:50.501493+00:00",
"action": "graduated",
"reviewer": "host-agent",
"notes": "The hierarchy-inversion mistake this session, requiring direct human correction rather than self-catching -- worth a durable lesson about resolving overlap correctly, not just avoiding literal duplication.",
"provisional": false,
"evidence_snapshot": [
"2026-08-01T06:53:43.608740+00:00"
],
"lessons_sha": "c7f4daa0894a"
}
],
"rejection_count": 0,
"accepted_at": "2026-08-01T06:53:50.501478+00:00",
"reviewer": "host-agent",
"rationale": "The hierarchy-inversion mistake this session, requiring direct human correction rather than self-catching -- worth a durable lesson about resolving overlap correctly, not just avoiding literal duplication."
}
Loading
Loading