Skip to content

fix(pi): rewrite adapter — inline TS hook, formula crash, decay tz bug - #24

Merged
codejunkie99 merged 2 commits into
codejunkie99:masterfrom
aliirz:fix/pi-adapter-rewrite
Apr 25, 2026
Merged

fix(pi): rewrite adapter — inline TS hook, formula crash, decay tz bug#24
codejunkie99 merged 2 commits into
codejunkie99:masterfrom
aliirz:fix/pi-adapter-rewrite

Conversation

@aliirz

@aliirz aliirz commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

What this fixes

Four distinct issues found after PR #17 was merged.


1. ModuleNotFoundError: No module named 'harness_manager' (the crash)

File: Formula/agentic-stack.rb

pkgshare.install was missing harness_manager/. install.sh dispatches to python3 -m harness_manager.cli but the module was never copied into the cellar, so agentic-stack pi crashes immediately for every brew user before doing anything at all.

Fix: add harness_manager to pkgshare.install.


2. memory-hook.ts — complete rewrite

PR #17's approach had four problems:

Problem Impact
fileURLToPath(import.meta.url) for path resolution jiti leaves import.meta.url undefined in CJS-transform mode → HOOK_SCRIPT path silently breaks → no logging at all
spawn(python3, [pi_post_tool.py]) per tool_result subprocess overhead + 3s timeout machinery on every single tool call including read, ls, grep
No pre-filtering Python spawned for read/find/ls/grep — pure noise
cc._is_success, cc._importance etc. Private underscore imports break silently on any refactor

New approach (matches the working reference setup from a live project):

  • process.cwd() for all paths — no import.meta.url
  • All scoring / reflection logic is inline TypeScript — no Python subprocess per tool call
  • Direct fs.appendFileSync to AGENT_LEARNINGS.jsonl
  • Pre-filter: bash / edit / write only — mirrors Claude Code's "^(Bash|Edit|Write)$" matcher
  • Low-importance bash successes (imp <= 3) also skipped to keep the log signal-rich
  • Typed inputs via isBashToolResult / isEditToolResult / isWriteToolResult (pi's own exported type guards)
  • cachedSha typed string | undefined so the git cache actually works
  • session_shutdown handler runs auto_dream.py on quit/new/resume — mirrors Claude Code's Stop hook. No more silent dream cycle gap between harnesses.

3. adapter.json — drop from_stack pi_post_tool.py entry

The hook is now self-contained TypeScript. pi_post_tool.py is no longer deployed as an explicitly-managed adapter file. It remains in the brain template for standalone/debug use.


4. decay.py — timezone-aware vs naive datetime crash

auto_dream.py → decay_old_entries() compared a timezone-aware cutoff against naive timestamp strings from AGENT_LEARNINGS.jsonl, crashing with:

TypeError: can't compare offset-naive and offset-aware datetimes

This crash was latent before but our new session_shutdown hook surfaces it on every clean pi exit.

Fix: use datetime.now(timezone.utc) for the cutoff and normalise any naive timestamps to UTC before comparison.


Testing

  • Fresh ./install.sh pi on a clean project: .pi/extensions/memory-hook.ts installed, .pi/skills symlinked, install.json correct
  • ./install.sh doctor → green
  • ./install.sh status → correct output
  • python3 .agent/memory/auto_dream.py runs without error
  • 41/41 existing tests pass

Closes #13 (properly this time).

Fixes four distinct issues found after PR codejunkie99#17 was merged:

## 1. ModuleNotFoundError crash on brew install (Formula)
Formula/agentic-stack.rb pkgshare.install was missing harness_manager/.
install.sh dispatches to `python3 -m harness_manager.cli` but the
module was never copied into the cellar, causing an immediate crash for
every brew user regardless of adapter.

## 2. memory-hook.ts — complete rewrite (approach was wrong)
PR codejunkie99#17's hook used:
  - fileURLToPath(import.meta.url) → jiti leaves this undefined in
    CJS-transform mode, silently breaking HOOK_SCRIPT path resolution
  - spawn(python3, [pi_post_tool.py]) per tool_result → subprocess
    overhead + timeout complexity for every single tool call
  - No pre-filtering → Python was spawned for read/find/ls/grep too
  - Private cc._is_success / cc._importance etc. imports → breaks
    silently on any refactor of claude_code_post_tool.py

New approach (matches the working life-international reference setup):
  - process.cwd() for all path resolution — no import.meta.url
  - All importance / pain_score / action / reflection logic is inline
    TypeScript — no Python subprocess per tool call
  - Direct fs.appendFileSync to AGENT_LEARNINGS.jsonl
  - Pre-filter: bash / edit / write only (mirrors Claude Code's
    "^(Bash|Edit|Write)$" matcher). Low-importance bash successes
    (imp <= 3) are also skipped to keep the log signal-rich.
  - Typed inputs via isBashToolResult / isEditToolResult /
    isWriteToolResult type guards (exported from pi's public API)
  - cachedSha typed as string | undefined so the git cache actually works
  - session_shutdown handler runs auto_dream.py on quit/new/resume,
    mirroring Claude Code's Stop hook. cron is now an optional fallback.

## 3. adapter.json — remove from_stack pi_post_tool.py entry
The hook is self-contained TypeScript. pi_post_tool.py is no longer
deployed as an explicitly-managed adapter file. It remains in the brain
template for standalone/debug use.

## 4. decay.py — timezone-aware vs naive datetime crash
auto_dream.py → decay_old_entries() compared a timezone-aware cutoff
(datetime.now()) against naive timestamps from AGENT_LEARNINGS.jsonl,
crashing with TypeError. Fixed by using datetime.now(timezone.utc) and
normalising naive timestamps to UTC before comparison.

Tested: fresh install on lumen-review, 41/41 tests pass, doctor green.
@aliirz
aliirz marked this pull request as draft April 24, 2026 20:43
@aliirz
aliirz marked this pull request as ready for review April 24, 2026 21:18
@codejunkie99 codejunkie99 added bug Something isn't working High Priority labels Apr 25, 2026
…c dream cycle

Follow-up fixes for PR codejunkie99#24 surfaced by independent verification (memory-hook
type cross-reference + codex review). Each fix is annotated with the failure
mode it addresses.

P0 — session_shutdown filter rejected every event
    Pi's `SessionShutdownEvent` is `{ type: "session_shutdown" }` with no
    `reason` field (verified in pi-coding-agent's types.d.ts and the emit
    site at agent-session.js:1638). The hook's `DREAM_REASONS.has(event.reason)`
    filter therefore evaluated `has(undefined) === false` for every event, so
    `auto_dream.py` never ran. Drop the filter; shutdown only fires once on
    process exit. Add a re-entrancy guard for defence in depth.

P1 — edit reflection always lost the diff
    Hook accessed `event.input.edits[0]` but Pi's `EditToolInput` is flat
    `{ path, oldText, newText }` (no `edits` array — that's MultiEdit on
    Claude Code). Reflections silently degraded to `Edited <path>` with no
    old/new content for the dream-cycle clusterer to grip. Use the flat
    fields directly.

P1 — naive-local Python timestamps + UTC decay = silent drift
    Decay's "naive == UTC" assumption is correct only if writers emit UTC.
    They didn't: post_execution, on_failure, learn, graduate, promote,
    review_state, render_lessons all wrote naive-local. Switch every writer
    to `datetime.now(timezone.utc).isoformat()` and teach every reader
    (salience, show._human_age / _daily_counts / failing_skills /
    last_dream_cycle, on_failure._count_recent_failures,
    review_state._age_factor, archive) to normalise naive timestamps to
    UTC before comparing.

P1 — one bad user regex disabled every user pattern
    Pre-fix: build one combined RegExp per list, catch any error, return
    null for both. A single typo in `hook_patterns.json` silently dropped
    all user rules. Now: validate per-fragment, incremental merge — same
    posture as `claude_code_post_tool.py`'s `_filter_valid` /
    `_build_with_fallback`.

P1 — auto_dream lost entries that landed mid-cycle
    Original PR fixed the truncate-before-lock race in `_write_entries` but
    not the read-modify-write window: an `append_jsonl()` between
    `_load_entries()` and `_write_entries(kept)` would be truncated away.
    Hold a single LOCK_EX on the episodic log across the entire cycle via
    `_episodic_locked()`. Mutually exclusive with `_episodic_io.append_jsonl`
    (same flock target).

P1 — salience over-scored future-skewed legacy rows
    Legacy naive-local timestamps re-interpreted as UTC can read as a few
    hours in the future during the migration window. `timedelta.days` then
    went negative and `recency = 10 - age*0.3` exceeded the intended cap.
    Floor age at 0; clamp recency to ≤ 10.

P2 — _cachedSha went stale across `git commit` inside a session
    Cache for the lifetime of pi was a perf win but recorded the pre-commit
    SHA on every entry after a mid-session commit. Invalidate on bash
    commands matching `git <subcmd>` for HEAD-moving subcommands.
    `[^|;&]*?` allows option flags between `git` and the subcommand
    (`git -c key=val checkout main`, `git -C path switch dev`). Includes
    `switch` which an earlier draft missed.

P3 — test_pi_install_creates_symlink_and_syncs_hook misnamed
    Asserted that `pi_post_tool.py` was synced via from_stack — the entry
    PR codejunkie99#24 removed. Rename the test, drop the obsolete assertion, document
    in the comment that the .py still ships in the brain template for
    standalone use.

P3 — decay archive filename used local date while cutoff was UTC
    Asymmetric. `archive_{date}.jsonl` now uses UTC date.

Plus tests/test_decay_timezone.py — six tests pinning the shapes decay must
handle: aware UTC old/recent, naive old, mixed mid-stream, malformed,
archive-filename-uses-UTC. Catches the original crash and the migration-era
silent-drift cases.

Test suite: 47 / 47 pass (was 41 before; +6 new). Lock semantics verified
manually (open-then-flock-then-ftruncate; full read-modify-write window
held under a single fd). SHA-invalidation regex spot-checked against
12 git command shapes.
@codejunkie99
codejunkie99 merged commit 0d64e23 into codejunkie99:master Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working High Priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pi adapter is missing automatic tool-call logging

2 participants