From 1d42aa77f90e6400712b94d4e10ec4f1e7a300eb Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sun, 12 Jul 2026 01:04:24 +0000 Subject: [PATCH 1/9] fix(memory): defer vacuum until exclusive maintenance --- src/memory/store.rs | 37 ++----------------------------- tests/memory_suite/memory_test.rs | 22 +++++++++++++++--- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/src/memory/store.rs b/src/memory/store.rs index 23eede59..e8dc9ab0 100644 --- a/src/memory/store.rs +++ b/src/memory/store.rs @@ -562,13 +562,8 @@ impl<'a> MemoryStore<'a> { } pub async fn remove_fact(&self, fact_id: i64) -> Result { - let changed = self - .with_immediate_tx("remove_fact", self.remove_fact_inner(fact_id)) - .await?; - if changed { - self.incremental_vacuum().await?; - } - Ok(changed) + self.with_immediate_tx("remove_fact", self.remove_fact_inner(fact_id)) + .await } async fn remove_fact_inner(&self, fact_id: i64) -> Result { @@ -2306,34 +2301,6 @@ impl<'a> MemoryStore<'a> { self.mark_bank_dirty(category.as_str()).await } - async fn incremental_vacuum(&self) -> Result<()> { - let freelist_pages = { - let mut rows = self - .conn - .query("PRAGMA freelist_count", ()) - .await - .map_err(|e| db_error("incremental_vacuum", e))?; - let row = rows - .next() - .await - .map_err(|e| db_error("incremental_vacuum", e))? - .ok_or_else(|| { - db_message("incremental_vacuum", "freelist_count returned no rows") - })?; - row.get::(0) - .map_err(|e| db_error("incremental_vacuum", e))? - }; - if freelist_pages <= 0 { - return Ok(()); - } - - self.conn - .execute_batch(&format!("PRAGMA incremental_vacuum({freelist_pages});")) - .await - .map_err(|e| db_error("incremental_vacuum", e))?; - Ok(()) - } - async fn mark_bank_dirty(&self, bank_name: &str) -> Result<()> { // `updated_at` doubles as an optimistic-concurrency token: `rebuild_dirty_banks` only // clears a marker whose value still matches the row it snapshotted. Since diff --git a/tests/memory_suite/memory_test.rs b/tests/memory_suite/memory_test.rs index 5a5c17d7..b0f8e08a 100644 --- a/tests/memory_suite/memory_test.rs +++ b/tests/memory_suite/memory_test.rs @@ -1309,7 +1309,7 @@ async fn compact_vectors_keep_recall_ordering_after_bank_rebuild() { } #[tokio::test] -async fn remove_fact_incremental_vacuum_reclaims_blob_pages() { +async fn remove_fact_defers_vacuum_while_peer_connections_are_live() { let (db, tmp) = make_memory_store().await; let store = MemoryStore::new(db.conn()); let db_path = tmp.path().join("tracedecay.db"); @@ -1351,6 +1351,7 @@ async fn remove_fact_incremental_vacuum_reclaims_blob_pages() { .await .unwrap(); let size_after_insert = std::fs::metadata(&db_path).unwrap().len(); + let (peer, _) = Database::open(&db_path).await.unwrap(); for fact_id in fact_ids { assert!(store.remove_fact(fact_id).await.unwrap()); @@ -1362,8 +1363,23 @@ async fn remove_fact_incremental_vacuum_reclaims_blob_pages() { let size_after_delete = std::fs::metadata(&db_path).unwrap().len(); assert!( - size_after_delete < size_after_insert, - "incremental_vacuum should shrink the DB file after deleting compact HRR blobs; before={size_after_insert}, after={size_after_delete}" + scalar_i64(&db, "PRAGMA freelist_count").await > 0, + "fact deletion must leave page reclamation for exclusive maintenance" + ); + assert_eq!( + scalar_i64(&peer, "SELECT COUNT(*) FROM memory_facts").await, + 0, + "a peer opened before deletion must remain usable" + ); + let (fresh, _) = Database::open(&db_path).await.unwrap(); + assert_eq!( + scalar_i64(&fresh, "SELECT COUNT(*) FROM memory_facts").await, + 0, + "a fresh peer must open after repeated fact deletion" + ); + assert!( + size_after_delete >= size_after_insert, + "online deletion must not compact a live peer store; before={size_after_insert}, after={size_after_delete}" ); } From 2d06a4d1b4a1bcd9386f68155f62b8e07b589683 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sun, 12 Jul 2026 01:10:49 +0000 Subject: [PATCH 2/9] fix(hermes): preserve replay identity during compression --- scripts/hermes_plugin_unit_check.py | 1 + src/agents/hermes/templates/plugin_init.py | 2 +- src/sessions/lcm/compression.rs | 51 +++++++++++++++-- tests/agent_suite/agent_test.rs | 10 +++- tests/session_suite/lcm_compression.rs | 65 ++++++++++++++++++++++ 5 files changed, 122 insertions(+), 7 deletions(-) diff --git a/scripts/hermes_plugin_unit_check.py b/scripts/hermes_plugin_unit_check.py index 4d09874e..da4e52f6 100644 --- a/scripts/hermes_plugin_unit_check.py +++ b/scripts/hermes_plugin_unit_check.py @@ -161,6 +161,7 @@ def run_checks(work: Path): sys.path.insert(0, str(plugin_dir.parent)) plugin = __import__("tracedecay") assert Path(plugin.__file__).resolve() == (plugin_dir / "__init__.py").resolve() + assert plugin.STANDARD_HERMES_LCM_PROVIDER == "hermes" ok("plugin package imports standalone (no hermes on sys.path)") header = (plugin_dir / "__init__.py").read_text(encoding="utf-8").splitlines()[0] diff --git a/src/agents/hermes/templates/plugin_init.py b/src/agents/hermes/templates/plugin_init.py index b4c5aedf..d1ab40ca 100644 --- a/src/agents/hermes/templates/plugin_init.py +++ b/src/agents/hermes/templates/plugin_init.py @@ -316,7 +316,7 @@ def _resolve_auxiliary_client(agent=None): "tracedecay_lcm_preflight", )) -STANDARD_HERMES_LCM_PROVIDER = "cursor" +STANDARD_HERMES_LCM_PROVIDER = "hermes" LCM_PROVIDER_LOCAL_TOOL_NAMES = frozenset(( "tracedecay_lcm_compress", diff --git a/src/sessions/lcm/compression.rs b/src/sessions/lcm/compression.rs index 5cdbdc56..0de23a4c 100644 --- a/src/sessions/lcm/compression.rs +++ b/src/sessions/lcm/compression.rs @@ -1866,6 +1866,16 @@ async fn ingest_active_messages( let original_content = message_content_value(message); let storage_text = message_storage_text(&original_content); let search_text = message_content(message); + if message + .get("lcm_summary_node_id") + .and_then(Value::as_str) + .is_some_and(|node_id| !node_id.is_empty()) + { + let mut replay = message.clone(); + replay["role"] = Value::String(role); + replay_messages.push(replay); + continue; + } if security::ignore_message_reason_with_compiled(&search_text, &compiled_ignore_patterns) .is_some() { @@ -1874,15 +1884,26 @@ async fn ingest_active_messages( replay_messages.push(replay); continue; } - let message_id = message + let explicit_message_id = message .get("id") .or_else(|| message.get("message_id")) .and_then(Value::as_str) .filter(|value| !value.is_empty()) - .map_or_else( - || deterministic_message_id(provider, session_id, idx, &role, &storage_text), - str::to_string, - ); + .map(str::to_string); + let stored_message_id = match (explicit_message_id.as_ref(), message.get("store_id")) { + (None, Some(store_id)) => match store_id.as_i64() { + Some(store_id) => { + message_id_for_store_id(conn, provider, session_id, store_id).await? + } + None => None, + }, + _ => None, + }; + let message_id = explicit_message_id + .or(stored_message_id) + .unwrap_or_else(|| { + deterministic_message_id(provider, session_id, idx, &role, &storage_text) + }); let existing_state = existing_active_message_state(conn, provider, &message_id).await?; let ordinal = if let Some(existing) = existing_state.as_ref() { existing.ordinal @@ -1969,6 +1990,26 @@ async fn ingest_active_messages( }) } +async fn message_id_for_store_id( + conn: &Connection, + provider: &str, + session_id: &str, + store_id: i64, +) -> Result, LcmError> { + let mut rows = conn + .query( + "SELECT message_id + FROM lcm_raw_messages + WHERE provider = ?1 AND session_id = ?2 AND store_id = ?3", + params![provider, session_id, store_id], + ) + .await?; + Ok(match rows.next().await? { + Some(row) => Some(row.get(0)?), + None => None, + }) +} + fn message_content_value(message: &Value) -> Value { message .get("content") diff --git a/tests/agent_suite/agent_test.rs b/tests/agent_suite/agent_test.rs index 42b368fd..8f1cd8bc 100644 --- a/tests/agent_suite/agent_test.rs +++ b/tests/agent_suite/agent_test.rs @@ -1199,6 +1199,14 @@ fn test_hermes_user_install_writes_single_plugin() { skill.contains("instead of querying"), "Hermes skill must warn against querying .tracedecay databases directly" ); + assert!( + skill.contains("Do not invent per-key CLI flags") + && skill.contains("\"mode\":\"explore\"") + && skill.contains("\"max_nodes\":20") + && skill.contains("supported modes are `explore` and `plan`") + && skill.contains("`--max-tokens` or `--paths`"), + "Hermes skill must give schema-safe context fallback guidance" + ); assert_hermes_config_enables_tracedecay_memory(&home.path().join(".hermes/config.yaml")); assert!(!home.path().join(".hermes/profiles").exists()); @@ -1268,7 +1276,7 @@ fn test_hermes_plugin_init_snapshot_matches_embedded_asset() { hasher.update(body.as_bytes()); assert_eq!( hex::encode(hasher.finalize()), - "d92997d9bef2c033022ac3b8ee559720ce08779099d2d8b58df6e5d849d428df", + "954dfa34aad7753f3ad6ff5e536061066e8339f3ee167e65ea3f27ca5a879575", "templates/plugin_init.py payload hash changed — verify the edit is intentional and update this snapshot" ); } diff --git a/tests/session_suite/lcm_compression.rs b/tests/session_suite/lcm_compression.rs index 7864a5c4..1b1511b1 100644 --- a/tests/session_suite/lcm_compression.rs +++ b/tests/session_suite/lcm_compression.rs @@ -1187,6 +1187,71 @@ async fn active_structured_content_survives_preflight_and_noop_compress_replay() ); } +#[tokio::test] +async fn idless_compression_replay_does_not_reingest_existing_raw_messages() { + let tmp = TempDir::new().unwrap(); + let db = open_lcm_db(&tmp).await; + insert_session(&db, "cursor", "session-idless-replay").await; + + let initial = db + .lcm_preflight(preflight_request( + "cursor", + "session-idless-replay", + vec![ + json!({"role": "user", "content": "old user context"}), + json!({"role": "assistant", "content": "old assistant context"}), + json!({"role": "user", "content": "fresh user context"}), + json!({"role": "assistant", "content": "fresh assistant context"}), + ], + Some(100), + )) + .await + .unwrap(); + assert_eq!( + db.lcm_status("cursor", Some("session-idless-replay")) + .await + .unwrap() + .raw_message_count, + 4 + ); + + let mut request = compress_request( + "cursor", + "session-idless-replay", + LcmSummarizerMode::Fake { + summary_text: "condensed old context".into(), + }, + ); + request.messages = initial.replay_messages; + let compressed = db.lcm_compress(request).await.unwrap(); + assert_eq!(compressed.summary_nodes_created, 1); + assert!(compressed.replay_messages[0]["lcm_summary_node_id"].is_string()); + assert!( + compressed + .replay_messages + .iter() + .skip(1) + .all(|message| message["store_id"].is_number()) + ); + + db.lcm_preflight(preflight_request( + "cursor", + "session-idless-replay", + compressed.replay_messages, + Some(100), + )) + .await + .unwrap(); + assert_eq!( + db.lcm_status("cursor", Some("session-idless-replay")) + .await + .unwrap() + .raw_message_count, + 4, + "replaying TraceDecay's own summary/tail must not duplicate raw history" + ); +} + #[tokio::test] async fn active_replay_preserves_top_level_fields_that_collide_with_storage_metadata() { let tmp = TempDir::new().unwrap(); From 157f6c136d9a0dd9b88f0563b83c7a6501116983 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sun, 12 Jul 2026 01:10:49 +0000 Subject: [PATCH 3/9] fix(agents): document JSON tool fallback arguments --- src/agents/hermes/templates/skill.md | 12 ++++++++++++ src/agents/mod.rs | 1 + tests/agent_suite/cli_args_contract_test.rs | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/src/agents/hermes/templates/skill.md b/src/agents/hermes/templates/skill.md index 50a14dda..f62b083e 100644 --- a/src/agents/hermes/templates/skill.md +++ b/src/agents/hermes/templates/skill.md @@ -20,6 +20,18 @@ shows parameters). Hermes tool calls already run through this CLI under the hood execution path without the plugin wrapper. Fall back to it instead of querying `.tracedecay` databases directly or abandoning tracedecay. +Do not invent per-key CLI flags or enum values from memory. Preserve the native +tool's exact JSON schema through `--args`; for example: + +```sh +tracedecay tool context --args '{"task":"trace project routing","mode":"explore","max_nodes":20,"include_code":true}' +``` + +For `context`, the supported modes are `explore` and `plan`, and its result +budget is expressed with `max_nodes` / `max_code_blocks`, not guessed flags such +as `--max-tokens` or `--paths`. When uncertain, run +`tracedecay tool --help` before invoking the fallback. + ## Storage and project identity Hermes may keep its own host files under its Hermes home, but that path never diff --git a/src/agents/mod.rs b/src/agents/mod.rs index a54529fe..1ed323c1 100644 --- a/src/agents/mod.rs +++ b/src/agents/mod.rs @@ -825,6 +825,7 @@ or the server is disconnected, every tool is also available as a shell command: cli_fallback_args_invocation_lit!(), " \ (`tracedecay tool` lists all tools, `tracedecay tool --help` shows parameters). \ +Pass schema fields inside the JSON object; never invent per-key flags or enum values from memory. \ Fall back to that CLI instead of querying `.tracedecay` databases directly or abandoning tracedecay." ); diff --git a/tests/agent_suite/cli_args_contract_test.rs b/tests/agent_suite/cli_args_contract_test.rs index 1f36bf04..9395bc0f 100644 --- a/tests/agent_suite/cli_args_contract_test.rs +++ b/tests/agent_suite/cli_args_contract_test.rs @@ -44,6 +44,11 @@ fn prompt_rules_teach_the_json_args_contract() { !rules.contains(" --key value"), "prompt rules must not lead with the per-key grammar" ); + let source = read_repo_file("src/agents/mod.rs"); + assert!( + source.contains("never invent per-key flags or enum values from memory"), + "CLI fallback prompt rules must prohibit guessed flags and enum values" + ); } #[test] From 89f416b1bd8a0dc4a471fadcf90d37f9f19dc7c8 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sun, 12 Jul 2026 01:30:01 +0000 Subject: [PATCH 4/9] test(hermes): expect native provider identity --- tests/hermes_suite/lcm_bridge.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/hermes_suite/lcm_bridge.rs b/tests/hermes_suite/lcm_bridge.rs index bcd593a5..ce6ec8c9 100644 --- a/tests/hermes_suite/lcm_bridge.rs +++ b/tests/hermes_suite/lcm_bridge.rs @@ -1233,7 +1233,7 @@ args_index = argv.index("--args") args = json.loads(argv[args_index + 1]) assert args == { "format": "json", - "provider": "cursor", + "provider": "hermes", "fresh_tail_count": 64, "leaf_chunk_tokens": 20000, "dynamic_leaf_chunk_enabled": False, @@ -1318,7 +1318,7 @@ assert argv[1:6] == ["tool", "--project", "/tmp/project", "tracedecay_lcm_sessio args = json.loads(argv[argv.index("--args") + 1]) assert args == { "format": "json", - "provider": "cursor", + "provider": "hermes", "session_id": "session-b", "old_session_id": "session-c", "boundary_reason": "compression", @@ -1427,7 +1427,7 @@ else: assert args == { "format": "json", "response_handle_project_root": "/tmp/project", - "provider": "cursor", + "provider": "hermes", "fresh_tail_count": 64, "leaf_chunk_tokens": 20000, "dynamic_leaf_chunk_enabled": False, From 42389dc6182324c04ea4a192d69d1920a3e93752 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sun, 12 Jul 2026 02:51:41 +0000 Subject: [PATCH 5/9] fix(hooks): keep Codex steering within compact budget --- src/hooks/steering.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/hooks/steering.rs b/src/hooks/steering.rs index 3fbff1b2..aee062ef 100644 --- a/src/hooks/steering.rs +++ b/src/hooks/steering.rs @@ -132,13 +132,6 @@ pub fn build_codex_session_context_for_workspace( tracedecay_diagnostics for fresh errors or pass captured output to \ tracedecay_diagnose; both map errors to symbols and callers.\n", ); - s.push_str( - "Specialists: tracedecay-code-explorer, tracedecay-code-health-auditor, \ - tracedecay-session-historian, tracedecay-runtime-storage-doctor, \ - tracedecay-cross-host-integration-auditor, tracedecay-change-risk-reviewer, \ - tracedecay-usage-intelligence-analyst, tracedecay-automation-auditor. \ - Delegate matching read-only research; the parent owns changes and releases.\n", - ); s.push_str(crate::agents::CLI_FALLBACK_PROMPT_RULES); s.push('\n'); append_codex_recall_and_registry_guidance(&mut s); From 246427a628e38b1ac0c418876efccbfe45beec2b Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sun, 12 Jul 2026 02:58:42 +0000 Subject: [PATCH 6/9] fix(daemon): bound background task shutdown --- src/daemon.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 82211385..ddd368b2 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1568,12 +1568,22 @@ async fn run_foreground_unix(socket_path: PathBuf) -> Result<()> { let clients_drained = drain_client_tasks(&mut client_tasks, DAEMON_TASK_ABORT_DEADLINE).await; // Client setup and in-flight requests may create schedulers or project // servers. Sweep owned background tasks only after all client work drains. - engine.shutdown_background_tasks().await; - if !in_flight_drained || !clients_drained { + let background_tasks_drained = timeout( + DAEMON_TASK_ABORT_DEADLINE, + engine.shutdown_background_tasks(), + ) + .await + .is_ok(); + if !in_flight_drained || !clients_drained || !background_tasks_drained { + let outcome = if background_tasks_drained { + "client_drain_timeout" + } else { + "background_task_timeout" + }; log_daemon_event( "daemon_shutdown", &[ - ("outcome", "client_drain_timeout".to_string()), + ("outcome", outcome.to_string()), ( "deadline_secs", DAEMON_CLIENT_DRAIN_DEADLINE.as_secs().to_string(), From 42d7ce8e8f18288f1902be9502ab512cf54ac067 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sun, 12 Jul 2026 03:08:51 +0000 Subject: [PATCH 7/9] fix(daemon): bound runtime teardown --- src/daemon.rs | 16 +++------------- src/main.rs | 7 ++++++- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index ddd368b2..82211385 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1568,22 +1568,12 @@ async fn run_foreground_unix(socket_path: PathBuf) -> Result<()> { let clients_drained = drain_client_tasks(&mut client_tasks, DAEMON_TASK_ABORT_DEADLINE).await; // Client setup and in-flight requests may create schedulers or project // servers. Sweep owned background tasks only after all client work drains. - let background_tasks_drained = timeout( - DAEMON_TASK_ABORT_DEADLINE, - engine.shutdown_background_tasks(), - ) - .await - .is_ok(); - if !in_flight_drained || !clients_drained || !background_tasks_drained { - let outcome = if background_tasks_drained { - "client_drain_timeout" - } else { - "background_task_timeout" - }; + engine.shutdown_background_tasks().await; + if !in_flight_drained || !clients_drained { log_daemon_event( "daemon_shutdown", &[ - ("outcome", outcome.to_string()), + ("outcome", "client_drain_timeout".to_string()), ( "deadline_secs", DAEMON_CLIENT_DRAIN_DEADLINE.as_secs().to_string(), diff --git a/src/main.rs b/src/main.rs index 130e7431..a82ea57e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -178,7 +178,12 @@ fn async_main() -> tracedecay::errors::Result<()> { .map_err(|e| tracedecay::errors::TraceDecayError::Config { message: format!("failed to start async runtime: {e}"), })?; - runtime.block_on(run(cli)) + let result = runtime.block_on(run(cli)); + // Runtime drop waits indefinitely for blocking tasks. Daemon integrations + // can leave OS-backed watcher work behind after their async handles abort, + // so bound teardown after the command's own graceful shutdown completes. + runtime.shutdown_timeout(std::time::Duration::from_secs(2)); + result } fn render_dynamic_command_help(args: &[String]) -> bool { From 95959f6f92bfae44e7a87695c8737a0e765d0973 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sun, 12 Jul 2026 03:15:22 +0000 Subject: [PATCH 8/9] test(hermes): support installed upstream venv layout --- scripts/hermes_stock_integration.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/hermes_stock_integration.sh b/scripts/hermes_stock_integration.sh index ba9ed814..d67354e1 100755 --- a/scripts/hermes_stock_integration.sh +++ b/scripts/hermes_stock_integration.sh @@ -36,12 +36,17 @@ fi echo "== stock hermes ref: $(git -C "$HERMES_UPSTREAM_DIR" rev-parse HEAD 2>/dev/null || echo unknown)" echo "== tracedecay binary: $TRACEDECAY_BIN ($("$TRACEDECAY_BIN" --version))" -# Upstream venv with upstream's exact-pinned lock (idempotent). -if [ ! -x "$HERMES_UPSTREAM_DIR/.venv/bin/python" ]; then +# Upstream source installs use either `.venv` (uv) or `venv` (Hermes installer). +if [ -x "$HERMES_UPSTREAM_DIR/.venv/bin/python" ]; then + HERMES_VENV="$HERMES_UPSTREAM_DIR/.venv" +elif [ -x "$HERMES_UPSTREAM_DIR/venv/bin/python" ]; then + HERMES_VENV="$HERMES_UPSTREAM_DIR/venv" +else echo "== creating stock hermes venv (uv sync --frozen --no-dev)" (cd "$HERMES_UPSTREAM_DIR" && uv sync --frozen --no-dev) + HERMES_VENV="$HERMES_UPSTREAM_DIR/.venv" fi -HERMES_PYTHON="$HERMES_UPSTREAM_DIR/.venv/bin/python" +HERMES_PYTHON="$HERMES_VENV/bin/python" STAGE="$(mktemp -d -t hermes-stock-XXXXXX)" trap 'rm -rf "$STAGE"' EXIT @@ -71,7 +76,7 @@ echo "== stock plugin manager / context engine / memory provider / dispatch chec echo "== hermes plugins list" PLUGINS_LIST="$(cd "$HERMES_UPSTREAM_DIR" && HOME="$FAKE_HOME" COLUMNS=200 \ - timeout 120 "$HERMES_UPSTREAM_DIR/.venv/bin/hermes" plugins list)" + timeout 120 "$HERMES_VENV/bin/hermes" plugins list)" echo "$PLUGINS_LIST" | grep tracedecay echo "$PLUGINS_LIST" | grep tracedecay | grep -q enabled echo "ok - hermes plugins list shows tracedecay enabled" From a67c1ec3b700d639e8f8d547c33d4beb4e73014b Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sun, 12 Jul 2026 03:28:00 +0000 Subject: [PATCH 9/9] fix(hooks): preserve compact agent discovery --- src/hooks/steering.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hooks/steering.rs b/src/hooks/steering.rs index aee062ef..5ff72944 100644 --- a/src/hooks/steering.rs +++ b/src/hooks/steering.rs @@ -132,6 +132,12 @@ pub fn build_codex_session_context_for_workspace( tracedecay_diagnostics for fresh errors or pass captured output to \ tracedecay_diagnose; both map errors to symbols and callers.\n", ); + s.push_str( + "Agents: tracedecay-code-explorer,tracedecay-code-health-auditor,\ + tracedecay-session-historian,tracedecay-runtime-storage-doctor,\ + tracedecay-cross-host-integration-auditor,tracedecay-change-risk-reviewer,\ + tracedecay-usage-intelligence-analyst,tracedecay-automation-auditor\n", + ); s.push_str(crate::agents::CLI_FALLBACK_PROMPT_RULES); s.push('\n'); append_codex_recall_and_registry_guidance(&mut s);