mirror of
https://github.com/akitaonrails/ai-memory.git
synced 2026-10-02 03:24:46 +08:00
fix(install-hooks)+docs: preserve capture-assistant on re-apply; 2.3 release-audit doc fixes
Post-audit of release/2.3 (v2.2.2..HEAD) before the 2.3.0 release. Fix (S1, the one code finding): install-hooks re-applied with no --capture-assistant flag silently stripped an existing assistant-capture opt-in — most visibly through the new `ai-memory run` auto-wire, which always re-applies without the flag. install-hooks now preserves an already-baked --capture-assistant on a bare apply (Claude Code + Codex, via the same existing-config read that prompt-capture already uses); an explicit flag still forces it on. Regression tests: bare re-apply preserves the opt-in; a fresh install without the flag stays off. (The underlying non-preservation is also latent on main and can be backported to a 2.2.x patch if desired.) Docs (release-readiness): - README: add the agent-messaging.md row to the Docs table (new feature + 4 MCP tools shipped without a README entry); note run-as-preferred/auto-wire. - ARCHITECTURE config reference: document capture_assistant, backfill_on_start, run_autowire + their AI_MEMORY_* env overrides. - install.md: add an "Upgrading to 2.3.0" note (the two default-on behaviors + opt-outs) and a forward-only/backup-before-rollback note. - design-boot-backfill.md: reconcile the now-answered "Open questions" with the as-built implementation (path B, on-by-default, capped). - design-rules-promotion.md: drop the stale "(2.1)" target label (untargeted, still unimplemented). - CHANGELOG: add the install-hooks preservation Fixed entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
da8d07dcc9
commit
9c2e793503
@@ -91,6 +91,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
OpenAI-compatible contract Copilot documents for chat (#739).
|
||||
|
||||
### Fixed
|
||||
- `install-hooks --apply` now preserves an existing `--capture-assistant` opt-in
|
||||
on a bare re-apply (one with no `--capture-assistant` flag). Previously a
|
||||
refresh without the flag silently stripped assistant capture; this most
|
||||
affected the new `ai-memory run` auto-wire, which always re-applies without the
|
||||
flag. The flag still explicitly enables it; an unset flag now keeps whatever is
|
||||
already installed (Claude Code and Codex).
|
||||
- `install-hooks --apply` no longer aborts the entire install when the hook
|
||||
bearer token cannot be persisted under the data dir. This bit the docker
|
||||
wrapper, where `data_dir` is `/data` — a container volume the host hooks
|
||||
|
||||
@@ -343,7 +343,8 @@ diagram, crate breakdown, schema notes, and invariants.
|
||||
| [`docs/cookbook.md`](docs/cookbook.md) | **Task-oriented cheat sheet.** "I want to do X" → how: recall prior work, keep a rule a project must follow, import an existing knowledge base (OKF norms/specs) and have a project read a specific document, and get two agents/repos working together. Start here if you're unsure what ai-memory can do for you. |
|
||||
| [`docs/install.md`](docs/install.md) | **Installation cookbook.** Every agent CLI, every alternative (curl, source build, no-docker, no-auth), and the server-on-a-different-machine (homelab/LAN) walkthrough. Read after the Quick start if your setup doesn't match the happy path. |
|
||||
| [`docs/usage.md`](docs/usage.md) | Handoffs, proactive memory queries, slim routing snippet + managed Agent Skills, migration from other memory tools, web UI, raw-wiki inspection, and rules-vs-facts workflow. |
|
||||
| [`docs/managed-workstreams.md`](docs/managed-workstreams.md) | Optional `ai-memory run` continuity across Claude Code, Codex, OpenCode, OpenCode 2 beta, Pi, Crush, Kimi Code, Command Code, Kiro CLI v2/v3, OMP, Grok Build CLI, and Antigravity CLI: automatic harness selection, native resume, argument forwarding, ledger search, privacy, and recovery. |
|
||||
| [`docs/managed-workstreams.md`](docs/managed-workstreams.md) | Optional `ai-memory run` continuity across Claude Code, Codex, OpenCode, OpenCode 2 beta, Pi, Crush, Kimi Code, Command Code, Kiro CLI v2/v3, OMP, Grok Build CLI, and Antigravity CLI: automatic harness selection, native resume, argument forwarding, ledger search, privacy, and recovery. The preferred way to launch — it auto-installs a harness's hooks + MCP on first run. |
|
||||
| [`docs/agent-messaging.md`](docs/agent-messaging.md) | Cross-project agent-to-agent messaging: a directed, claim-once inbox/queue so an agent in one project can hand a self-contained request to an agent in another, plus the on-start "you have mail" notice. Four `memory_message_*` MCP tools + `ai-memory message` CLI. |
|
||||
| [`docs/managed-harness-contributions.md`](docs/managed-harness-contributions.md) | Protocol and acceptance bar for contributors adding managed resume, read-only transcript import, and startup context delivery to another harness. |
|
||||
| [`docs/marker-file.md`](docs/marker-file.md) | `.ai-memory.toml` workspace/project routing for multi-client trees, mono-repos, worktrees, and work/personal separation. |
|
||||
| [`docs/auto-scope.md`](docs/auto-scope.md) | `[auto_scope]` modes for shared servers: default single-slot routing, session-aware isolation, and multi-user `per_actor` behavior. |
|
||||
|
||||
@@ -496,6 +496,20 @@ pub fn run(config: &Config, mut args: InstallHooksArgs) -> Result<()> {
|
||||
break cross-agent continuity."
|
||||
);
|
||||
}
|
||||
// Preserve an existing `--capture-assistant` opt-in on a bare re-apply. There
|
||||
// is no negative flag, so a re-run without `--capture-assistant` (notably the
|
||||
// `ai-memory run` auto-wire, which always passes it off) must NOT silently
|
||||
// downgrade a user who had enabled assistant capture. An explicit
|
||||
// `--capture-assistant` still forces it on; this only fills in the unset case
|
||||
// from what is already installed. Gated to the agents where assistant capture
|
||||
// is allowed (Claude Code, Codex), whose configs share the nested-hooks shape.
|
||||
if args.apply
|
||||
&& !args.capture_assistant
|
||||
&& capture_assistant_allowed(args.agent)
|
||||
&& existing_capture_assistant_opt_in(&args)
|
||||
{
|
||||
args.capture_assistant = true;
|
||||
}
|
||||
if args.apply {
|
||||
// #446: settle the capture failure mode before any agent-specific
|
||||
// work, and say which mode is in force. A protection the operator
|
||||
@@ -871,6 +885,54 @@ fn baked_claude_prompt_capture(existing: &str) -> Option<bool> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether the currently-installed config for this agent already bakes the
|
||||
/// `--capture-assistant` opt-in. Used to preserve that opt-in across a bare
|
||||
/// re-apply (e.g. `ai-memory run` auto-wire) instead of dropping it. Only the
|
||||
/// agents where assistant capture is allowed (Claude Code, Codex) reach here,
|
||||
/// and both write the nested-hooks JSON shape.
|
||||
fn existing_capture_assistant_opt_in(args: &InstallHooksArgs) -> bool {
|
||||
existing_agent_config(args)
|
||||
.as_deref()
|
||||
.and_then(baked_capture_assistant)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// `Some(true|false)` when `existing` is a recognizable ai-memory install and
|
||||
/// whether its Stop command carries `--capture-assistant`; `None` when it is not
|
||||
/// an ai-memory install at all (so there is nothing to preserve).
|
||||
fn baked_capture_assistant(existing: &str) -> Option<bool> {
|
||||
let document: serde_json::Value = serde_json::from_str(existing).ok()?;
|
||||
let hooks = document.get("hooks")?.as_object()?;
|
||||
let mut saw_ai_memory = false;
|
||||
let mut has_marker = false;
|
||||
for entries in hooks.values().filter_map(serde_json::Value::as_array) {
|
||||
for entry in entries {
|
||||
// Nested (`hooks:[{command}]`) or flat (`{command}`) shape.
|
||||
let nested = entry.get("hooks").and_then(serde_json::Value::as_array);
|
||||
let commands: Vec<&str> = match nested {
|
||||
Some(inner) => inner
|
||||
.iter()
|
||||
.filter_map(|e| e.get("command").and_then(|c| c.as_str()))
|
||||
.collect(),
|
||||
None => entry
|
||||
.get("command")
|
||||
.and_then(|c| c.as_str())
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
for command in commands {
|
||||
if hook_command_is_ours(command) {
|
||||
saw_ai_memory = true;
|
||||
if command.contains("--capture-assistant") {
|
||||
has_marker = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
saw_ai_memory.then_some(has_marker)
|
||||
}
|
||||
|
||||
/// Read the config file `--apply` will update for the selected agent.
|
||||
fn existing_agent_config(args: &InstallHooksArgs) -> Option<String> {
|
||||
let path = if let Some(path) = &args.config_file {
|
||||
@@ -7965,6 +8027,73 @@ model = "gpt-5"
|
||||
);
|
||||
}
|
||||
|
||||
fn claude_apply_args(
|
||||
settings: &std::path::Path,
|
||||
hooks_dir: &std::path::Path,
|
||||
capture_assistant: bool,
|
||||
) -> InstallHooksArgs {
|
||||
InstallHooksArgs {
|
||||
agent: AgentChoice::ClaudeCode,
|
||||
apply: true,
|
||||
capture_assistant,
|
||||
server_url: Some("http://127.0.0.1:49374".to_string()),
|
||||
config_file: Some(settings.to_path_buf()),
|
||||
hooks_dir: Some(hooks_dir.to_path_buf()),
|
||||
..default_hook_args()
|
||||
}
|
||||
}
|
||||
|
||||
/// Regression for the auto-wire capture-downgrade (post-audit S1): a bare
|
||||
/// `--apply` without `--capture-assistant` — exactly what `ai-memory run`
|
||||
/// auto-wire issues — must PRESERVE a user's existing assistant-capture
|
||||
/// opt-in, not silently strip it.
|
||||
#[test]
|
||||
fn a_bare_reapply_preserves_an_existing_capture_assistant_opt_in() {
|
||||
let home = TempDir::new().unwrap();
|
||||
let cfg_dir = TempDir::new().unwrap();
|
||||
let settings = cfg_dir.path().join("settings.json");
|
||||
std::fs::write(&settings, "{}").unwrap();
|
||||
let config = crate::config::Config::load(None, Some(home.path().to_path_buf())).unwrap();
|
||||
let hooks_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../hooks");
|
||||
|
||||
run(&config, claude_apply_args(&settings, &hooks_dir, true)).expect("first install");
|
||||
assert!(
|
||||
std::fs::read_to_string(&settings)
|
||||
.unwrap()
|
||||
.contains("--capture-assistant"),
|
||||
"the explicit opt-in must be baked on the first install"
|
||||
);
|
||||
|
||||
// A bare re-apply (the auto-wire shape: capture_assistant = false).
|
||||
run(&config, claude_apply_args(&settings, &hooks_dir, false)).expect("bare re-apply");
|
||||
assert!(
|
||||
std::fs::read_to_string(&settings)
|
||||
.unwrap()
|
||||
.contains("--capture-assistant"),
|
||||
"a bare re-apply must preserve the existing --capture-assistant opt-in"
|
||||
);
|
||||
}
|
||||
|
||||
/// The preserve logic must not false-positive: a fresh install without the
|
||||
/// flag stays off.
|
||||
#[test]
|
||||
fn a_fresh_install_without_the_flag_leaves_assistant_capture_off() {
|
||||
let home = TempDir::new().unwrap();
|
||||
let cfg_dir = TempDir::new().unwrap();
|
||||
let settings = cfg_dir.path().join("settings.json");
|
||||
std::fs::write(&settings, "{}").unwrap();
|
||||
let config = crate::config::Config::load(None, Some(home.path().to_path_buf())).unwrap();
|
||||
let hooks_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../hooks");
|
||||
|
||||
run(&config, claude_apply_args(&settings, &hooks_dir, false)).expect("fresh install");
|
||||
assert!(
|
||||
!std::fs::read_to_string(&settings)
|
||||
.unwrap()
|
||||
.contains("--capture-assistant"),
|
||||
"a fresh install without the flag must not enable assistant capture"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hook_source_candidates_include_native_package_dir() {
|
||||
let candidates = hook_source_candidates(
|
||||
|
||||
@@ -550,6 +550,22 @@ prefixed `AI_MEMORY_*`.
|
||||
bind = "127.0.0.1:49374"
|
||||
log_level = "info"
|
||||
|
||||
# Capture / launch UX (all default-on where noted). Each has an AI_MEMORY_* env
|
||||
# override (AI_MEMORY_CAPTURE_ASSISTANT / AI_MEMORY_BACKFILL_ON_START /
|
||||
# AI_MEMORY_RUN_AUTOWIRE).
|
||||
capture_assistant = false # server-side opt-in: honor a Claude Code / Codex
|
||||
# client's sanitized assistant-final-message marker
|
||||
# on Stop (#196). Client half is baked separately by
|
||||
# `install-hooks --capture-assistant`.
|
||||
backfill_on_start = true # on first SessionStart in a brand-new (empty) project,
|
||||
# import that project's existing local harness history
|
||||
# once so hooks-mid-project isn't amnesiac. Only ever
|
||||
# bootstraps an empty project; hard-capped. `ai-memory
|
||||
# backfill` runs it by hand.
|
||||
run_autowire = true # `ai-memory run <harness>` auto-installs that harness's
|
||||
# hooks + MCP on first launch if missing (idempotent,
|
||||
# one-time per harness+version). Also `--no-autowire`.
|
||||
|
||||
[decay] # M8 retention params
|
||||
lambda = 0.02 # ↓ to forget less aggressively
|
||||
sigma = 0.6 # ↑ to reward query-hits more
|
||||
|
||||
@@ -218,17 +218,22 @@ cross-link them — doctor is the ongoing check, backfill is the one-time bootst
|
||||
- **Multi-session**: two concurrent boots → exactly one import (lease claim-once), the
|
||||
other no-ops — the invariant-#16 concurrency shape, proven at integration level.
|
||||
|
||||
## Open questions for the plan
|
||||
## Open questions — resolved as built
|
||||
|
||||
1. **Ingest path A vs B** — confirm the lifecycle-free workstream-import endpoint is worth
|
||||
adding versus an observation replay. (Recommend A for the cursor.)
|
||||
2. **Shared-server default** — auto-on everywhere, or auto-on only in the single-operator
|
||||
posture with explicit `ai-memory backfill` required on multi-user servers?
|
||||
3. **Notice delivery** — Claude discards SessionStart stdout; reuse the same
|
||||
"deliver on first user prompt" path the hot-context block already uses for Kimi/Claude?
|
||||
4. **Consolidation quality** — should a first pass gate on a small eval that backfilled
|
||||
pre-hook ledgers produce pages of comparable quality to forward capture before making
|
||||
full-project bootstrap the default, or ship behind the cap and iterate?
|
||||
These were the pre-implementation questions; the shipped feature (see the **As-built
|
||||
note** at the top) resolved each:
|
||||
|
||||
1. **Ingest path A vs B** — resolved to **B** (replay through `/hook`). A live smoke proved
|
||||
path A populated the workstream continuity ledger, not the searchable memory pipeline
|
||||
(observation count stayed zero), so it was abandoned.
|
||||
2. **Shared-server default** — shipped **on by default** with `AI_MEMORY_BACKFILL_ON_START=false`
|
||||
/ `--no`-style opt-out; the emptiness gate makes it safe on any posture (it only ever
|
||||
bootstraps an empty project).
|
||||
3. **Notice delivery** — the automatic path runs detached and silent (like Claude Code's
|
||||
own auto-memory); the summary shows on a manual `ai-memory backfill`. Surfacing it in
|
||||
the next session's on-start context remains a possible follow-up.
|
||||
4. **Consolidation quality** — shipped **behind the hard cap** (newest 25 sessions / 50k
|
||||
events) and iterating; a dedicated eval remains a follow-up.
|
||||
|
||||
## Semver
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Design: Promoting Memory into Governed AGENTS.md Rules (2.1)
|
||||
# Design: Promoting Memory into Governed AGENTS.md Rules
|
||||
|
||||
*Status: design, targeting the `release/2.1` line. Not implemented yet — this
|
||||
is the balance research the feature needs before code.*
|
||||
*Status: design proposal — not implemented. (Originally scoped against the 2.1
|
||||
line; that and 2.2 shipped without it, so it is un-targeted and still open —
|
||||
this is the balance research the feature needs before code.)*
|
||||
|
||||
## 1. The idea, and the trap
|
||||
|
||||
|
||||
+22
-1
@@ -2376,7 +2376,28 @@ the wrapper requires `<url>.sha256` unless
|
||||
|
||||
When the upgraded server starts, it applies SQLite schema migrations and
|
||||
pending wiki-structure migrations automatically. No manual database
|
||||
reset or wiki rewrite is required for normal upgrades.
|
||||
reset or wiki rewrite is required for normal upgrades. Migrations are
|
||||
forward-only: after a newer version has applied its schema, an older binary
|
||||
will refuse to open that data dir (it fails closed rather than risk
|
||||
corruption), so take a `ai-memory backup` before upgrading if you might need
|
||||
to roll back to the previous version.
|
||||
|
||||
### Upgrading to 2.3.0
|
||||
|
||||
2.3.0 is a normal forward upgrade (the only new migration, `V64`, just adds the
|
||||
cross-project `agent_messages` table — nothing existing is altered or removed).
|
||||
Two capture/UX conveniences are **on by default**; both are additive and
|
||||
non-destructive, but worth knowing about for your first session after upgrading:
|
||||
|
||||
- **First `ai-memory run <harness>` auto-installs that harness's hooks + MCP** if
|
||||
they were not already wired (idempotent, one-time per harness; it preserves
|
||||
your existing hook config, including a `--capture-assistant` opt-in). Disable
|
||||
with `ai-memory run --no-autowire` or `AI_MEMORY_RUN_AUTOWIRE=false`.
|
||||
- **The first session in a brand-new (empty) project imports that project's
|
||||
existing local harness history once**, so installing ai-memory mid-project is
|
||||
not amnesiac. It only ever runs on an empty project (never touches one that
|
||||
already has captured memory) and is hard-capped. Disable with
|
||||
`AI_MEMORY_BACKFILL_ON_START=false`; run it by hand with `ai-memory backfill`.
|
||||
|
||||
If the server runs on another host, `ai-memory upgrade` refreshes only
|
||||
the local wrapper, local image, and local hook scripts. Redeploy the
|
||||
|
||||
Reference in New Issue
Block a user