Fire hook memory on signals only

Task 6674 · Done · Coder · created · updated

Epic 6659: Project memory, open the pipe, honest trust. This is child C5 (improvement 5, PRD S5). PRD S5-4's demotion test lives in C3. It builds on C0's plan gate. Sources: PRD (Task 6660) §3.5, and Architecture (Task 6661) §1 C5 and §2.4. ## Original requirement (verbatim) "It is important that all memory related mechanism stays behind the feature flag on the billing plan." … "Each later subtask ships its change inside this gate and adds a test proving the mechanism is inert with the term off." "5. Hook tier earns its lines. `tools/project_memory_tier.py` 304–361, `tools/context_router.py` 791–815 / 1197–1266, `apps/jaahd/fleet_account_settings.json` (UserPromptSubmit wiring): drop the per-turn prompt match; fire on Bash command-prefix and Edit/Read anchored-path matches only; for gotcha/recipe carry up to 300 characters of body instead of the lead; count a unit surfaced in 20 sessions and never read as unused, the same demotion the prompt channel uses. Done means: a session replay shows no `[memory]` line on UserPromptSubmit, a Bash match shows the body, and the unused counter increments from hook surfacings." ## Acceptance criteria (verbatim, PRD §3.5) - **S5-1 No `[memory]` line on UserPromptSubmit.** Context-router directives on UserPromptSubmit are unchanged. Only the learned-memory lines go. - **S5-2 Signal-only matching.** `[memory]` lines fire only on a PreToolUse Bash command-prefix match and on an anchored-path match for Edit/Write/MultiEdit and Read (the Read/Grep/Glob hook keeps its path mode). Keyword-only matches no longer fire on Bash. - **S5-3 Body, not lead, for gotcha/recipe.** A gotcha/recipe line carries up to 300 characters of body. qa/env keep the lead. Text is sanitised as today and cut on a word boundary with an ellipsis, so it is readable and never a fragment of a few characters. - **S5-4 (console part only here)** The Memory tab's Injected counter counts hook surfacings. The demotion test is in C3. - **S5-5 Reaches nodes.** The tier/router changes reach nodes through the jaah package that Jaah Deploy publishes. The packaged `/usr/lib/skynetd/tools/` copies are what run. - **S5-6 Inert** (S0-7): with the term off, the tier emits no `[memory]` line even if a stale local bundle exists. - **Q3** Single-home: limits are named constants in `apps/api/app/project_memory/vocabulary.py`. The node-side mirror is pinned by a test. ## Approach (Architect §1 C5, §2.4; settled) - `tools/context_router.py:1186-1198` `mode_prompt`: drop the `_learned(...)` call. Directives are unchanged. - `tools/project_memory_tier.py::match` (`:304-332`): a non-codemap unit fires only on `path_hit or cmd_hit`. Keywords may add to the score but never fire alone. - **Body excerpt.** - The CP bundle (`apps/api/app/project_memory/bundle.py:58-119`) serves `lead` for gotcha/recipe as up to `HOOK_BODY_MAX = 300` characters of body, through the existing `lead()` with a per-kind max. qa/env keep `HOOK_LEAD_MAX = 200`. Both constants live in `vocabulary.py`. - The cut is on a word boundary with `…`. - The bundle docstring's "never a body" becomes "a bounded body excerpt for gotcha/recipe". - The tier keeps a node-side per-kind `LEAD_MAX` mirror, pinned by extending `tools/tests/test_terminal_types_parity.py`. - **Bundle wire (Architect §2.4).** `units[].lead` stays a string. A CP test builds a maximum-size bundle (served-unit cap × 300-character excerpts) and asserts it stays under the tier's `BUNDLE_MAX_BYTES` (512 KiB), trimming the served count if needed. - **S5-6.** Plan off at dispatch means no arm, so no `JAAH_PROJECT_MEMORY`, so `_learned` returns `[]`. The test writes a stale bundle file, unsets the env, and asserts there is no `[memory]` line on each hook mode. The PR body states this pins an existing gate as a regression guard. A task already running is bounded by the 410 → empty-bundle refresh (≤60 s). - `fleet_account_settings.json` needs no change: UserPromptSubmit stays for directives. - **S5-5.** The change ships in the jaah package via Jaah Deploy. Shared edit points (rebase expected): `vocabulary.py`, `tools/tests/test_terminal_types_parity.py` (C4 also extends it), and `bundle.py`. Credentials & reach (verbatim rows from Architecture §4): - All unit/API/web tests and `./dc check full` | Nothing beyond the worktree and its `./dc` stack's Postgres | verified: worktree present, gate runs in-slot. - Dev console checks (S5-1/3/4/5), operator sign-in on `https://app.dev.jaah.ai` | `jaah/debug/main/operator-login` | verified: on the roster, `task_credential_get` succeeded, and the dev health probe returned 200. - PR merge and Jaah Deploy watch (E1) | `jaah-forge` (authenticated per task) | verified. - Dev oracles needing the new node code (S5-1/3/5 dev) | Dev nodes running a jaah package built from the C5 merge | deferred: the package does not exist until this child merges and Jaah Deploy publishes it. Confirm the node's package version on the console Nodes page before the dev check. The router/tier tests stand as the oracle meanwhile. Out of scope: - From the epic, verbatim: "configuring the production memory model credential (an operator action in Settings › Runners › Project memory, not code); retiring mem-2/mem-3 and purging mem-7834 (operator does it in the console); any change to the OMC notepad/project-memory tier; new embedding providers." - Make no change to non-memory context-router directives or their UserPromptSubmit wiring. - Do NOT change the codemap feed's exclusion (Task 6297). - Do NOT edit `use_pairs` or demotion (C3 owns S5-4's fix). - Make no migration. Done means: - S5-1 — a router test feeds a prompt that keyword-matches a unit and asserts no `[memory]` in the output while directives are still present. A session replay on dev (a fresh memory-ON runner transcript after the package lands) shows no `[memory]` attachment on any UserPromptSubmit. - S5-2 — router tests: a Bash command with a prefix match fires; a Bash command with keyword-only overlap does not; an Edit and a Read on an anchored path fire. - S5-3 — a tier test with a long gotcha body asserts the line carries ≤300 characters of body, ends on a word boundary with `…`, and is readable prose, not a mid-word fragment. A 300-raw-character mid-word cut FAILS. The bundle-size test passes. On dev, a runner's Bash command matching a gotcha shows the body text in the `[memory]` line. - S5-4 (console) — on dev, Project › Memory shows Injected rising for a hook-only unit after a session ends. - S5-5 — after deploy, a fresh dev runner transcript on a node with the new package shows S5-1…S5-3 behaviour. - S5-6 — an inert test with a stale bundle file and the env unset shows no `[memory]` line in any hook mode. Name it in the PR body. - Q3 — `HOOK_BODY_MAX`/`HOOK_LEAD_MAX` live only in `vocabulary.py`, and `test_terminal_types_parity.py` pins the tier mirror to them. - E1 — the PR is merged to develop through auto-merge with `./dc check full` green, and the Jaah Deploy run succeeded (or was superseded).

Outcome

Hook [memory] now fires only on Bash prefix or path matches, never on prompts; gotcha/recipe show a 300-char body excerpt. Fixed bundle byte-cap overshoot. Full gate green, PR #5625 merged; deploy superseded by the next run. No UI affected; dev runner checks wait for the package.

Pull request

Artifacts

Activity

  • Draft
  • Ready
  • Runner started
  • Runner ended (done)