Skip to content
Open
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
11 changes: 11 additions & 0 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ here so neither side drifts.

## Open

### From the 2026-09-03 long-session investigation

- [x] **Long sessions run dozens of tool calls with no memory contact — the per-turn gate keys off continuation prompts** ([#296](https://github.com/CryptoJones/omind/issues/296)) — _feat (guard)_ —
continuation-aware preflight + retry carry + a per-turn action budget in the
harness-agnostic core (injected on Claude `PostToolUse`, demanded as a re-arm
elsewhere); `doctor` gains `gate_continuity`.
- [ ] **CI: Windows test jobs fail on every run since 2026-08-26** ([#297](https://github.com/CryptoJones/omind/issues/297)) — _fix (journal, ai_usage)_ —
seven journal-rollup `PermissionError`s (an open handle before the archive
move) and the off-`PATH` CLI resolver test; neither job is a required check,
so the Windows matrix currently carries no signal.

### From the 2026-08-27 multi-agent review (code round — fixes in the working tree)

_A nine-slice review (memory core, MCP surface, mesh, enforcement, retrieval,
Expand Down
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- **Consult continuity across long sessions (#296).** Measured on a live box,
~40% of turns started with the consult gate auto-cleared and nothing
injected, because the preflight ranked notes against the prompt alone and a
long session's prompts are continuations (`retry`, `Yes please`, a task
notification). Two harness-agnostic controls in `guard`: (1) a
continuation-shaped prompt is retrieved against the prior turn's task plus the
agent's recent activity, and an identical `retry` inside two minutes (the
harness's own API auto-retry) carries the turn's gate state instead of
resetting it; (2) every allowed action counts against a per-turn budget
(`OMIND_GATE_ACTION_BUDGET`, default 25) after which the core surfaces a
relevant memory this session has not seen — injected via Claude Code's
`PostToolUse` `additionalContext` (`omind hook PostToolUse --harness claude`,
written by `omind setup`), or demanded as a re-arm at the next PreToolUse on
every other harness. Capped by `OMIND_GATE_MAX_REARM` (default 4) per turn.
`omind doctor` gains a `gate_continuity` check (7-day auto-clear rate, warns
at 40%); every decision is compliance-logged (`omi-gate-preflight`,
`omi-gate-carry`, `omi-gate-rearm`, `omi-gate-rearm-no-match`) and the whole
`omi-gate*` family is excluded from the fine-tune corpus and from the
OpenCode plugin's enforced denies.

### Fixed
- **Tool error text survives mcp >= 2.1 (#294).** mcp 2.1.x hands the client a
bare `Error executing tool <name>` for any exception that is not a deliberate
`ToolError`, which hid every anticipated failure — a missing note, an unsafe
name, a bad graph argument, and the stale-version conflict whose message is
what tells an agent to re-read before writing. The server now re-raises those
domain failures (`NoteError`, `NoteConflictError`, `ValueError`) as `ToolError`
at the tool boundary; a real crash stays masked as the SDK intends. `uv.lock`
moves to mcp 2.1.1 so a local run sees what CI sees.

### Fixed — from the 2026-08-27 multi-agent review (35 findings, all fixed;
full report in `docs/reviews/2026-08-27-multi-agent-review.md`)

Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,24 @@ broken hook can never wedge the agent.
a miss, so it still leaves the gate armed. Set `OMI_GATE_MISS_STRICT=1` to
restore the old force-a-consult-on-every-miss behavior. Hard rule-specific
prerequisites remain independent.
- **Consult continuity across a long session.** A continuation prompt
(`retry`, `go ahead`, a task notification — fewer than three meaningful
terms) carries no signal of its own, so it is retrieved against the **prior
turn's task plus the agent's recent activity**; the gate auto-clears only when
the vault has nothing for the work in progress. An identical `retry` re-sent
within two minutes is the harness's own API auto-retry and carries the
turn's gate state instead of resetting it. Inside a turn, every allowed
action counts against a budget (`OMIND_GATE_ACTION_BUDGET`, default 25);
at the budget the core retrieves against the turn's task and its action
trail, and if a relevant memory this session has not seen exists it is
surfaced — injected after the tool call where the harness can do that
(Claude Code `PostToolUse`), otherwise as a re-arm that demands exactly that
note before the next action (one `recall-note` clears it). No candidate →
the budget resets and the auto-clear is logged. At most
`OMIND_GATE_MAX_REARM` (default 4) mid-turn re-arms per turn, so a turn can
never be re-gated indefinitely. This lives in the harness-agnostic core, so
every adapter (Claude, Hermes, OpenCode, Codex, Gemini, DSH) inherits it.
`omind doctor` reports the 7-day auto-clear rate (`gate_continuity`).
- **The verifier.** Clearing the gate by reading *any* note isn't enough, so a
`PostToolUse` verifier judges whether the consult was actually **relevant** to
the turn's task — a deterministic keyword-overlap prefilter decides the clear
Expand Down
2 changes: 1 addition & 1 deletion docs/manual-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ these three and leaves the rest of the file alone:
"hooks": [
{
"type": "command",
"command": "omind hook PostToolUse --vault \"$HOME/Documents/Obsidian Vault\" --folder \"OMI\""
"command": "omind hook PostToolUse --vault \"$HOME/Documents/Obsidian Vault\" --folder \"OMI\" --harness claude"
}
]
}
Expand Down
9 changes: 8 additions & 1 deletion src/omind/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,12 @@ def build_parser() -> argparse.ArgumentParser:
help="the hook event name (Claude Code: PostToolUse/Stop/SessionStart; "
"Hermes Agent: pre_llm_call)",
)
hook.add_argument(
"--harness",
default="",
help="the calling harness (e.g. claude); enables the mid-turn recall "
"injection only where the harness's post-tool hook can inject context",
)
_add_vault_args(hook)

loop = sub.add_parser(
Expand Down Expand Up @@ -1573,7 +1579,8 @@ def _run_consolidate(args: argparse.Namespace) -> int:

def _run_hook(args: argparse.Namespace) -> int:
omi_dir = (args.vault / args.folder).expanduser()
return run_hook(args.event, omi_dir) # always 0; must never block the agent
# always 0; must never block the agent
return run_hook(args.event, omi_dir, harness=str(getattr(args, "harness", "") or ""))


def _run_loop(args: argparse.Namespace) -> int:
Expand Down
51 changes: 50 additions & 1 deletion src/omind/compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import os
import re
from collections import Counter
from datetime import datetime
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any

Expand Down Expand Up @@ -265,10 +265,59 @@ def recidivism_counts(events: list[dict[str, Any]] | None = None) -> Counter[str
"repo-work-read-git-rules",
"off-topic-consult",
"omi-gate",
"omi-gate-rearm",
"verify-reclose-floor",
}
)

#: The consult-gate continuity decisions (#296), all logged at UserPromptSubmit
#: or by the mid-turn budget. ``turns`` counts every turn the preflight judged.
_GATE_INJECT_RULE = "omi-gate-preflight"
_GATE_AUTO_CLEAR_RULES = frozenset({"omi-gate-no-match", "omi-gate-weak-match"})
_GATE_CARRY_RULE = "omi-gate-carry"
_GATE_REARM_RULE = "omi-gate-rearm"
_GATE_REARM_NO_MATCH_RULE = "omi-gate-rearm-no-match"


def gate_continuity(*, days: int = 7, now: datetime | None = None) -> dict[str, Any]:
"""Rollup of how often a turn started with memory injected vs. auto-cleared,
and what the mid-turn budget did, over the last ``days`` (#296)."""
since = (now or datetime.now()) - timedelta(days=max(0, days))
injected = auto_cleared = carried = denies = injects = no_match = 0
for event in read_events():
ts = str(event.get("ts") or "")
try:
if datetime.fromisoformat(ts) < since:
continue
except ValueError:
continue
rule_id = str(event.get("rule_id") or "")
outcome = str(event.get("outcome") or "")
if rule_id == _GATE_INJECT_RULE:
injected += 1
elif rule_id in _GATE_AUTO_CLEAR_RULES:
auto_cleared += 1
elif rule_id == _GATE_CARRY_RULE:
carried += 1
elif rule_id == _GATE_REARM_RULE:
if outcome == "inject":
injects += 1
else:
denies += 1
elif rule_id == _GATE_REARM_NO_MATCH_RULE:
no_match += 1
turns = injected + auto_cleared
return {
"turns": turns,
"injected": injected,
"auto_cleared": auto_cleared,
"carried": carried,
"auto_clear_pct": (100.0 * auto_cleared / turns) if turns else 0.0,
"rearm_denies": denies,
"rearm_injects": injects,
"rearm_no_match": no_match,
}


def summary() -> dict[str, Any]:
"""A compact rollup for ``omind doctor``: totals + the top recidivist rules."""
Expand Down
4 changes: 3 additions & 1 deletion src/omind/corpus.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ def corpus_examples() -> list[dict[str, Any]]:
examples: list[dict[str, Any]] = []
for event in compliance.read_events():
rule_id = str(event.get("rule_id") or "")
if not rule_id or rule_id == "omi-gate":
# The consult-gate family (the gate itself, its preflight auto-clears,
# the mid-turn budget) is ceremony, not a violation to learn from.
if not rule_id or rule_id.startswith("omi-gate"):
continue
tool = str(event.get("tool") or "action")
command = str(event.get("command") or "")
Expand Down
Loading
Loading