2.1.0 — quick-wins train: brief load, offline-durable capture, session-end verb

Three independently useful changes (IMPROVEMENT-PLANS #1/#3/#7), no schema changes:

- load --brief: token-budgeted cold-start digest (facts full, last-10 observations,
  scope+freshness, last session's key sections, agent-log lines, inbox count;
  ECHO_LOAD_BUDGET ~8000 chars, lowest-priority-first trimming). SessionStart hook
  injects the brief form; /echo-load keeps the full dump. All load reads (+ the
  sessions listing, which also feeds the heartbeat fallback) fetched in parallel.
- Offline capture durability: a fully-offline capture queues the WHOLE op as one
  semantic record; flush replays it through capture so routing/gate/aliasing re-run
  against the current index; a gate stop on replay is kept + flagged
  (needs_attention, surfaced by flush and load), never landed blind; update-path
  and ensure_daily_log writes ride safe_request; same-args captures dedupe.
- session-end: one call, one lock — session log -> agent-log line -> reflect
  proposals (gate-aware) -> optional scope set -> heartbeat LAST as the commit
  marker; dry-run default; ECHO_NOW pins HHMM; validation runs before any write.
  Stop hook nudge names the command and recognizes it as reflected.
- Fix: main() now catches the __main__ twin-module EchoError (via RuntimeError +
  .code) so helper-module errors exit with their intended codes, not tracebacks.

+19 e2e checks; all suites green. Plans renumbered: MCP container is 2.2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jason Stedwell
2026-07-28 22:34:42 -05:00
parent 1deef7299e
commit 446cd9a0ff
16 changed files with 702 additions and 81 deletions
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""echo_session.py — the session-end bundle: one call ends a session correctly. [2.1.0]
Ending a substantive session used to take four-plus separate invocations (session-log
PUT, Agent Log append, heartbeat PUT, optional scope set, optional reflect apply) —
a cost per session, and a partial failure left broken orientation state (log written
but heartbeat stale). This verb does all of it in one call, under ONE advisory-lock
acquisition, in a fixed order with the heartbeat written LAST as the commit marker:
a failure part-way leaves the previous pointer intact, never a dangling one.
Bundle shape (JSON object):
{
"slug": "echo-mcp-spec", # required, kebab-case
"log_body": "<full session-log markdown, frontmatter included>", # required
"agent_log_line": "- 2026-07-28: ...", # optional; derived when omitted
"scope": "new scope text", # optional -> scope set
"reflect": [ { ...PROPOSAL_SCHEMA... } ] # optional -> routed via capture
}
Dry-run by default (previews the whole plan, reflect included); --apply writes.
Times: the filename HHMM comes from $ECHO_NOW (HHMM) else the local clock; the date
from ECHO_TODAY via echo.today(). Offline: every step rides the offline queue, so a
session end during an outage queues durably instead of failing.
CLI: echo.py session-end <bundle.json> [--apply]
"""
from __future__ import annotations
import datetime as dt
import json
import os
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import echo # noqa: E402
SESSION_RE = re.compile(r"^\d{4}-\d{2}-\d{2}-\d{4}-[a-z0-9-]+\.md$")
SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
def _hhmm() -> str:
v = os.environ.get("ECHO_NOW", "").strip()
if v:
if not re.match(r"^\d{4}$", v):
raise echo.EchoError(f"session-end: $ECHO_NOW must be HHMM, got '{v}'", 2)
return v
return dt.datetime.now().strftime("%H%M")
def validate(bundle: dict) -> tuple[str, str]:
"""Return (session_path, agent_log_line) or raise EchoError(2). Validation runs
BEFORE any write — a bad bundle writes nothing at all."""
if not isinstance(bundle, dict):
raise echo.EchoError("session-end: bundle must be a JSON object", 2)
slug = str(bundle.get("slug", "")).strip()
if not SLUG_RE.match(slug):
raise echo.EchoError(f"session-end: slug must be kebab-case, got '{slug}'", 2)
if not str(bundle.get("log_body", "")).strip():
raise echo.EchoError("session-end: log_body is required (the full session-log markdown)", 2)
reflect = bundle.get("reflect")
if reflect is not None and not isinstance(reflect, list):
raise echo.EchoError("session-end: reflect must be a JSON array of proposals", 2)
fname = f"{echo.today()}-{_hhmm()}-{slug}.md"
if not SESSION_RE.match(fname):
raise echo.EchoError(f"session-end: derived filename '{fname}' violates the "
"canonical YYYY-MM-DD-HHMM-<slug>.md form", 2)
path = f"_agent/sessions/{fname}"
line = str(bundle.get("agent_log_line") or "").strip() \
or f"- {echo.today()}: session logged [[{path[:-3]}]]"
return path, line
def session_end(bundle: dict, apply: bool = False) -> int:
import echo_reflect
path, line = validate(bundle)
scope = str(bundle.get("scope") or "").strip()
proposals = bundle.get("reflect") or []
valid, errors = echo_reflect.validate(proposals)
for e in errors:
print(f"skip: {e}", file=sys.stderr)
print(f"session-end plan ({'APPLY' if apply else 'dry-run'}):")
print(f" 1. session log -> {path}")
print(f" 2. agent-log line: {line}")
if valid:
echo_reflect.classify(valid)
print(f" 3. reflect: {len(valid)} proposal(s)")
print(echo_reflect.preview(valid))
else:
print(" 3. reflect: (none)")
print(f" 4. scope set: {scope!r}" if scope else " 4. scope: (unchanged)")
print(f" 5. heartbeat -> {path} @ <now> (written LAST — the commit marker)")
if not apply:
print("\nsession-end: dry-run — re-run with --apply to write.")
return 0
import echo_concurrency
import echo_ops
steps: dict[str, str] = {}
with echo_concurrency.vault_lock():
# 1. The session log itself. A hard failure here aborts the whole bundle —
# nothing after it (including the heartbeat) runs, so orientation state
# can never point at a log that was never written.
echo.cmd_put(path, echo.temp_file(str(bundle["log_body"]).encode("utf-8")))
steps["session_log"] = "ok"
# 2. Agent-log line (best-effort by contract; queues itself when offline).
echo_ops.ensure_daily_log(line)
steps["agent_log"] = "ok"
# 3. Reflect proposals through the normal capture path (gate-aware).
if valid:
gated = 0
for p in valid:
if p.get("_action") == "error":
continue
bodyfile = echo.temp_file((p.get("body") or "").encode()) if p.get("body") else None
rc = echo_ops.capture(
p.get("kind"), p["title"], bodyfile,
aliases=p.get("aliases") or [], sources=p.get("sources") or [],
tags=p.get("tags") or [], date=p.get("date"),
domain=p.get("domain", "business"),
inbox=bool(p.get("inbox")) or not p.get("kind"))
gated += 1 if rc == 76 else 0
steps["reflect"] = f"{len(valid) - gated}/{len(valid)} applied" \
+ (f", {gated} gated (re-propose with --merge-into)" if gated else "")
else:
steps["reflect"] = "skipped (none)"
# 4. Scope switch (optional).
if scope:
echo.cmd_scope("set", scope)
steps["scope"] = "ok"
else:
steps["scope"] = "unchanged"
# 5. Heartbeat LAST — the commit marker for the whole bundle.
echo.cmd_put("_agent/heartbeat/last-session.md",
echo.temp_file(f"{path} @ {echo.now_iso()}\n".encode("utf-8")))
steps["heartbeat"] = "ok"
print("\nsession-end: done — "
+ "; ".join(f"{k}: {v}" for k, v in steps.items()))
return 0