Implement WARDEN-WP-0024 experiential memory and agent sessions.

Add phase-memory bridge, warden memory CLI, route/access/sign recording,
memory-aware worker planning with OpenRouter skip, tests, wiki, and AGENTS.md
orientation for Claude, Codex, Grok, and future agent sessions.
This commit is contained in:
tegwick 2026-07-02 23:40:45 +02:00
parent 2f532699fa
commit 04929e7981
9 changed files with 568 additions and 14 deletions

View file

@ -44,6 +44,11 @@ activity_app = typer.Typer(
no_args_is_help=True,
)
app.add_typer(activity_app, name="activity")
memory_app = typer.Typer(
help="Cross-runtime experiential memory via phase-memory (WARDEN-WP-0024)",
no_args_is_help=True,
)
app.add_typer(memory_app, name="memory")
console = Console()
err = Console(stderr=True)
@ -53,6 +58,30 @@ err = Console(stderr=True)
# Helpers
# ---------------------------------------------------------------------------
def _record_memory_episode(
*,
command: str,
outcome: str,
need: str = "",
route_id: str = "",
) -> None:
try:
from warden import memory as warden_memory
except ImportError:
return
if not warden_memory.enabled() or not warden_memory.memory_available():
return
try:
warden_memory.record_command_episode(
command=command,
outcome=outcome,
need=need,
route_id=route_id,
)
except RuntimeError:
return
def _load_cfg() -> WardenConfig:
try:
return load_config()
@ -128,6 +157,12 @@ def sign(
# cert_command interface: write cert text to stdout only
print(record.cert_path.read_text().strip())
_record_memory_episode(
command="sign",
outcome="resolved",
need=f"ssh cert {actor_name}",
route_id="ops-warden-ssh-cert",
)
# ---------------------------------------------------------------------------
@ -753,14 +788,30 @@ def route_find(
if output_json:
print(json.dumps([_entry_summary(e) for e in matches], indent=2))
if matches:
_record_memory_episode(
command="route find",
outcome="resolved",
need=query,
route_id=matches[0].id,
)
else:
_record_memory_episode(command="route find", outcome="skipped", need=query)
return
if not matches:
_record_memory_episode(command="route find", outcome="skipped", need=query)
console.print(
f"No routing match for {query!r}. "
"Try `warden route list --all` to browse all scenarios."
)
return
_record_memory_episode(
command="route find",
outcome="resolved",
need=query,
route_id=matches[0].id,
)
_print_entry_table(matches, f"Matches for {query!r}")
@ -1003,8 +1054,20 @@ def access(
if output_json:
print(json.dumps(_access_json(entry, expanded, gate, domain), indent=2))
_record_memory_episode(
command="access",
outcome="resolved",
need=need,
route_id=entry.id,
)
return
_record_memory_episode(
command="access",
outcome="resolved",
need=need,
route_id=entry.id,
)
console.print(f"[bold]{entry.title}[/bold] ([cyan]{entry.id}[/cyan])")
console.print(f" owner : {entry.owner_repo} ({entry.subsystem})")
@ -1305,3 +1368,58 @@ def worker_status_cmd() -> None:
console.print(f"timer : {st or 'unknown'}")
except Exception: # noqa: BLE001 — systemd may be absent (cron/other host)
console.print("timer : (systemd not available)")
# ---------------------------------------------------------------------------
# warden memory — cross-runtime experiential memory (WARDEN-WP-0024)
# ---------------------------------------------------------------------------
@memory_app.command("status")
def memory_status(
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
) -> None:
"""Show canonical phase-memory store status (metadata only)."""
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.status()
except RuntimeError as e:
err.print(f"[red]{e}[/red]")
raise typer.Exit(2)
if output_json:
print(json.dumps(payload, indent=2))
return
console.print(f"store_path : {payload.get('store_path', '')}")
console.print(f"profile_id : {payload.get('profile_id', '')}")
console.print(f"episode_count : {payload.get('episode_count', 0)}")
console.print(f"session_kinds : {payload.get('episode_counts_by_session_kind', {})}")
console.print(f"last_activation: {payload.get('last_activation_at') or ''}")
@memory_app.command("activate")
def memory_activate(
need: Annotated[str, typer.Option("--need", help="Optional routing need fingerprint source")] = "",
agent: Annotated[
Optional[str],
typer.Option("--agent", help="Agent id for session_kind warden.agent.<id> (claude, codex, grok, …)"),
] = None,
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
) -> None:
"""Activate bounded coordination memory for worker, operator, or agent sessions."""
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)
except RuntimeError as e:
err.print(f"[red]{e}[/red]")
raise typer.Exit(2)
if output_json:
print(json.dumps(payload, indent=2))
return
console.print(warden_memory.format_activation_summary(payload))