generated from coulomb/repo-seed
Enable implicit phase-memory activation on every warden command.
Load coordination memory by default via ensure_memory_context on app bootstrap and route/access flows; invalidate cache after episode writes. WARDEN_MEMORY=0 remains the opt-out. Document that warden memory activate is optional only.
This commit is contained in:
12
AGENTS.md
12
AGENTS.md
@@ -161,15 +161,19 @@ get wrong.
|
||||
ops-warden shares a **phase-memory** store across worker ticks, coding agent
|
||||
sessions, and operator CLI use.
|
||||
|
||||
**At session start** (Claude Code, Codex, Grok, or future agents):
|
||||
**Default:** phase-memory loads automatically on every `warden` command when
|
||||
`phase-memory` is available. No separate activation step is required.
|
||||
|
||||
**Agent sessions** should set runtime identity once per session:
|
||||
|
||||
```bash
|
||||
export WARDEN_AGENT_ID=grok # or claude, codex
|
||||
warden memory activate --json
|
||||
```
|
||||
|
||||
**During work:** use normal `warden route` / `warden access` / `warden sign`;
|
||||
episodes are recorded automatically unless `WARDEN_MEMORY=0`.
|
||||
Then use normal `warden route` / `warden access` / `warden sign` / `warden worker`.
|
||||
Episodes are recorded automatically unless `WARDEN_MEMORY=0`.
|
||||
|
||||
`warden memory activate` is optional introspection/refresh, not a prerequisite.
|
||||
|
||||
**Store:** `~/.local/share/warden/memory/` (override: `WARDEN_MEMORY_STORE`).
|
||||
|
||||
|
||||
@@ -54,6 +54,19 @@ console = Console()
|
||||
err = Console(stderr=True)
|
||||
|
||||
|
||||
@app.callback()
|
||||
def _bootstrap_memory(ctx: typer.Context) -> None:
|
||||
"""Implicitly load phase-memory for every warden command (opt-out: WARDEN_MEMORY=0)."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
return
|
||||
try:
|
||||
from warden import memory as warden_memory
|
||||
|
||||
warden_memory.ensure_memory_context(implicit=True)
|
||||
except Exception: # noqa: BLE001 — memory must never block warden commands
|
||||
return
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -783,6 +796,12 @@ def route_find(
|
||||
limit: Annotated[int, typer.Option("--limit", help="Max matches")] = 5,
|
||||
) -> None:
|
||||
"""Rank routing scenarios by keyword overlap with the query."""
|
||||
try:
|
||||
from warden import memory as warden_memory
|
||||
|
||||
warden_memory.ensure_memory_context(need=query, implicit=True)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
catalog = _load_catalog()
|
||||
matches = catalog.find(query, include_draft=all_entries, limit=limit)
|
||||
|
||||
@@ -1026,6 +1045,12 @@ def access(
|
||||
"""
|
||||
from warden.access import expand_handoff, policy_gate_status
|
||||
|
||||
try:
|
||||
from warden import memory as warden_memory
|
||||
|
||||
warden_memory.ensure_memory_context(need=need, implicit=True)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
catalog = _load_catalog()
|
||||
matches = catalog.find(need, include_draft=all_entries, limit=1)
|
||||
if not matches:
|
||||
@@ -1408,14 +1433,14 @@ def memory_activate(
|
||||
] = None,
|
||||
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
|
||||
) -> None:
|
||||
"""Activate bounded coordination memory for worker, operator, or agent sessions."""
|
||||
"""Inspect or refresh coordination memory (optional — memory loads by default)."""
|
||||
from warden import memory as warden_memory
|
||||
|
||||
if not warden_memory.memory_available():
|
||||
err.print(f"[red]{warden_memory._PHASE_MEMORY_ERROR}[/red]")
|
||||
raise typer.Exit(2)
|
||||
try:
|
||||
payload = warden_memory.activate(need=need, agent=agent)
|
||||
payload = warden_memory.activate(need=need, agent=agent, implicit=False)
|
||||
except RuntimeError as e:
|
||||
err.print(f"[red]{e}[/red]")
|
||||
raise typer.Exit(2)
|
||||
|
||||
@@ -10,6 +10,17 @@ _PHASE_MEMORY_ERROR = (
|
||||
"Install with: pip install phase-memory (or set PYTHONPATH to phase-memory/src)."
|
||||
)
|
||||
|
||||
# In-process cache: implicit activation is default; no separate `warden memory activate`
|
||||
# is required for normal route/access/worker/sign use within one CLI invocation tree.
|
||||
_CONTEXT_CACHE: dict[str, Any] | None = None
|
||||
_CONTEXT_CACHE_KEY: tuple[str, str] = ("", "")
|
||||
|
||||
|
||||
def _invalidate_context_cache() -> None:
|
||||
global _CONTEXT_CACHE, _CONTEXT_CACHE_KEY
|
||||
_CONTEXT_CACHE = None
|
||||
_CONTEXT_CACHE_KEY = ("", "")
|
||||
|
||||
|
||||
def _phase_memory():
|
||||
try:
|
||||
@@ -46,24 +57,76 @@ def status(environ: Mapping[str, str] | None = None) -> dict[str, Any]:
|
||||
return pm.OpsWardenMemoryStore.open(environ=environ).status()
|
||||
|
||||
|
||||
def memory_context_summary(activation: dict[str, Any] | None) -> dict[str, Any]:
|
||||
if not activation:
|
||||
return {"enabled": False}
|
||||
return {
|
||||
"enabled": True,
|
||||
"implicit": bool(activation.get("implicit")),
|
||||
"session_kind": activation.get("session_kind", ""),
|
||||
"episode_count": activation.get("episode_count", 0),
|
||||
"stabilized_route_id": (activation.get("stabilized_route") or {}).get("route_id", ""),
|
||||
"llm_calls_avoided": bool(activation.get("llm_calls_avoided")),
|
||||
"selected_episode_count": len(activation.get("selected_episodes") or ()),
|
||||
}
|
||||
|
||||
|
||||
def ensure_memory_context(
|
||||
need: str = "",
|
||||
*,
|
||||
agent: Optional[str] = None,
|
||||
session_id: str = "",
|
||||
environ: Mapping[str, str] | None = None,
|
||||
implicit: bool = True,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Load coordination memory for the current session (default, no extra command)."""
|
||||
global _CONTEXT_CACHE, _CONTEXT_CACHE_KEY
|
||||
|
||||
if not enabled(environ):
|
||||
return None
|
||||
if not memory_available():
|
||||
return None
|
||||
pm = _phase_memory()
|
||||
env = dict(environ or os.environ)
|
||||
if agent:
|
||||
env["WARDEN_AGENT_ID"] = agent
|
||||
kind = pm.resolve_session_kind(env)
|
||||
fingerprint = pm.need_fingerprint(need) if need else ""
|
||||
cache_key = (kind, fingerprint)
|
||||
if _CONTEXT_CACHE is not None and _CONTEXT_CACHE_KEY == cache_key:
|
||||
return _CONTEXT_CACHE
|
||||
try:
|
||||
activation = activate(need=need, agent=agent, session_id=session_id, environ=env)
|
||||
except RuntimeError:
|
||||
return None
|
||||
activation = {**activation, "implicit": implicit}
|
||||
_CONTEXT_CACHE = activation
|
||||
_CONTEXT_CACHE_KEY = cache_key
|
||||
return activation
|
||||
|
||||
|
||||
def activate(
|
||||
*,
|
||||
need: str = "",
|
||||
agent: Optional[str] = None,
|
||||
session_id: str = "",
|
||||
environ: Mapping[str, str] | None = None,
|
||||
implicit: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
pm = _phase_memory()
|
||||
env = dict(environ or os.environ)
|
||||
if agent:
|
||||
env["WARDEN_AGENT_ID"] = agent
|
||||
kind = pm.resolve_session_kind(env)
|
||||
return pm.activate_ops_warden_memory(
|
||||
activation = pm.activate_ops_warden_memory(
|
||||
pm.OpsWardenMemoryStore.open(environ=env),
|
||||
session_kind=kind,
|
||||
need=need,
|
||||
session_id=session_id,
|
||||
)
|
||||
if implicit:
|
||||
activation = {**activation, "implicit": True}
|
||||
return activation
|
||||
|
||||
|
||||
def record_command_episode(
|
||||
@@ -91,13 +154,16 @@ def record_command_episode(
|
||||
diagnostic_codes=diagnostic_codes,
|
||||
metadata=metadata,
|
||||
)
|
||||
return pm.record_session_event(pm.OpsWardenMemoryStore.open(environ=env), event)
|
||||
result = pm.record_session_event(pm.OpsWardenMemoryStore.open(environ=env), event)
|
||||
if result.get("valid"):
|
||||
_invalidate_context_cache()
|
||||
return result
|
||||
|
||||
|
||||
def worker_activation_context(need: str = "", environ: Mapping[str, str] | None = None) -> dict[str, Any]:
|
||||
env = dict(environ or os.environ)
|
||||
env["WARDEN_SESSION_KIND"] = "warden.worker"
|
||||
return activate(need=need, environ=env)
|
||||
return ensure_memory_context(need=need, environ=env, implicit=True) or activate(need=need, environ=env, implicit=True)
|
||||
|
||||
|
||||
def stabilized_route_for_need(need: str, environ: Mapping[str, str] | None = None) -> Optional[dict[str, Any]]:
|
||||
|
||||
@@ -602,7 +602,9 @@ def _memory_activation_for_message(message: dict) -> tuple[Optional[dict], str]:
|
||||
return None, ""
|
||||
query = str(message.get("subject", "") or message.get("body", ""))
|
||||
try:
|
||||
activation = warden_memory.worker_activation_context(query)
|
||||
activation = warden_memory.ensure_memory_context(need=query, implicit=True)
|
||||
if activation is None:
|
||||
activation = warden_memory.worker_activation_context(query)
|
||||
except RuntimeError:
|
||||
return None, ""
|
||||
from warden.memory import format_activation_summary
|
||||
|
||||
@@ -126,4 +126,18 @@ def test_memory_disabled_skips_recording(monkeypatch) -> None:
|
||||
|
||||
def test_default_store_path_uses_xdg(monkeypatch) -> None:
|
||||
monkeypatch.delenv("WARDEN_MEMORY_STORE", raising=False)
|
||||
assert str(store_path()).endswith("warden/memory")
|
||||
assert str(store_path()).endswith("warden/memory")
|
||||
|
||||
|
||||
def test_route_find_implicitly_activates_memory_without_explicit_command(tmp_path, monkeypatch) -> None:
|
||||
from warden.memory import ensure_memory_context
|
||||
|
||||
monkeypatch.setenv("WARDEN_MEMORY_STORE", str(tmp_path / "memory"))
|
||||
monkeypatch.delenv("WARDEN_AGENT_ID", raising=False)
|
||||
result = runner.invoke(app, ["route", "find", "ssh tunnel", "--json"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
activation = ensure_memory_context(need="ssh tunnel", implicit=True)
|
||||
assert activation is not None
|
||||
assert activation.get("implicit") is True
|
||||
assert status()["episode_count"] >= 1
|
||||
@@ -19,13 +19,18 @@ ticks, coding agent sessions, and operator CLI use.
|
||||
| Coding agent | `export WARDEN_AGENT_ID=claude` (or `codex`, `grok`, future ids) |
|
||||
| Operator CLI | default `warden.operator` when `WARDEN_AGENT_ID` is unset |
|
||||
|
||||
## Default Behavior
|
||||
|
||||
phase-memory is **on by default** (`WARDEN_MEMORY=1`). Every `warden` command
|
||||
implicitly loads the canonical store before route/access/worker/sign work. You do
|
||||
not need a separate activation command for normal use.
|
||||
|
||||
## Agent Session Orientation
|
||||
|
||||
At the start of a Claude Code, Codex, or Grok session that will call warden:
|
||||
For Claude Code, Codex, Grok, or future agents, set runtime identity once:
|
||||
|
||||
```bash
|
||||
export WARDEN_AGENT_ID=grok # or claude, codex
|
||||
warden memory activate --json
|
||||
```
|
||||
|
||||
Then use normal `warden route` / `warden access` commands. Episodes are recorded
|
||||
|
||||
Reference in New Issue
Block a user