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
@@ -83,8 +83,13 @@ def recall(query, limit: int = 8, as_json: bool = False) -> int:
# ----------------------------------------------------------- agent log -------
def ensure_daily_log(line: str) -> None:
"""Resilient: ensure today's daily note + its `## Agent Log` heading exist, then
idempotently append the line. Best-effort — never raises into the caller."""
idempotently append the line. Best-effort — never raises into the caller. Writes go
through the offline queue (2.1.0), so a mid-session outage queues the line instead
of silently dropping it. Offline edge accepted: if the daily note still doesn't
exist at replay time, the queued PATCH 400-drops with a loud warning — the agent-log
line is a convenience trace, not the memory itself."""
try:
import echo_queue
date = echo.today()
path = f"journal/daily/{date}.md"
status, body = echo.request("GET", echo.vault_url(path))
@@ -92,21 +97,22 @@ def ensure_daily_log(line: str) -> None:
ts, tb = echo.request("GET", echo.vault_url("journal/templates/daily-note-template.md"))
tmpl = tb.decode(errors="replace") if ts == 200 else f"# {date}\n\n## Agent Log\n\n## Related\n"
tmpl = tmpl.replace("{{date:YYYY-MM-DD}}", date).replace("{{DATE}}", date)
echo.request("PUT", echo.vault_url(path), data=tmpl.encode(),
headers={"Content-Type": "text/markdown"})
echo_queue.safe_request("PUT", echo.vault_url(path), data=tmpl.encode(),
headers={"Content-Type": "text/markdown"})
text = tmpl
else:
text = body.decode(errors="replace")
if not re.search(r"(?m)^## Agent Log\s*$", text):
echo.request("POST", echo.vault_url(path), data=b"\n\n## Agent Log\n",
headers={"Content-Type": "text/markdown"})
status, body = echo.request("GET", echo.vault_url(path))
if status == 200 and line in body.decode(errors="replace"):
text = body.decode(errors="replace") if status == 200 else ""
if status == 200 and not re.search(r"(?m)^## Agent Log\s*$", text):
echo_queue.safe_request("POST", echo.vault_url(path), data=b"\n\n## Agent Log\n",
headers={"Content-Type": "text/markdown"})
st2, body2 = echo.request("GET", echo.vault_url(path))
if st2 == 200 and line in body2.decode(errors="replace"):
return
echo.request("PATCH", echo.vault_url(path),
data=echo.normalize_patch_body((line + "\n").encode(), "append", "heading"),
headers={"Operation": "append", "Target-Type": "heading",
"Target": f"{date}::Agent Log", "Content-Type": "text/markdown"})
echo_queue.safe_request(
"PATCH", echo.vault_url(path),
data=echo.normalize_patch_body((line + "\n").encode(), "append", "heading"),
headers={"Operation": "append", "Target-Type": "heading",
"Target": f"{date}::Agent Log", "Content-Type": "text/markdown"})
except Exception as exc:
print(f"echo_ops: agent-log skipped ({exc})", file=sys.stderr)
@@ -148,20 +154,25 @@ def _dated_block(today_s: str, body_text: str) -> tuple[str, str]:
def _append_to_existing(path: str, kind: str, today_s: str, body_text: str) -> None:
# Writes route through the offline queue (2.1.0) so a mid-capture outage queues the
# update instead of silently losing it. (A fully-offline capture never reaches here —
# the short-circuit in capture() queues the whole op as one semantic record.)
import echo_queue
text = links.get_text(path) or ""
h1 = links.first_h1(text) or path.rsplit("/", 1)[-1][:-3]
heading = LOG_HEADING.get(kind, "Notes")
if not re.search(rf"(?m)^##\s+{re.escape(heading)}\s*$", text):
echo.request("POST", echo.vault_url(path), data=f"\n\n## {heading}\n".encode(),
headers={"Content-Type": "text/markdown"})
echo_queue.safe_request("POST", echo.vault_url(path), data=f"\n\n## {heading}\n".encode(),
headers={"Content-Type": "text/markdown"})
bullet, block = _dated_block(today_s, body_text)
status, body = echo.request("GET", echo.vault_url(path))
if status == 200 and bullet in body.decode(errors="replace"):
return
echo.request("PATCH", echo.vault_url(path),
data=echo.normalize_patch_body((block + "\n").encode(), "append", "heading"),
headers={"Operation": "append", "Target-Type": "heading",
"Target": f"{h1}::{heading}", "Content-Type": "text/markdown"})
echo_queue.safe_request(
"PATCH", echo.vault_url(path),
data=echo.normalize_patch_body((block + "\n").encode(), "append", "heading"),
headers={"Operation": "append", "Target-Type": "heading",
"Target": f"{h1}::{heading}", "Content-Type": "text/markdown"})
echo.cmd_fm(path, "updated", json.dumps(today_s))
@@ -213,7 +224,35 @@ def capture(kind: str | None, title: str, file_arg: str | None, status_v: str =
return done("inbox", "inbox/captures/inbox.md", ok=rc == 0)
slug = idx_mod.slugify(title)
index = idx_mod.load()
# Offline short-circuit (2.1.0). The routed path needs the entity index and several
# round-trips; when the vault is unreachable, queue the WHOLE capture as one semantic
# record — flush replays it back through this function against the index as it is at
# replay time, so routing, the duplicate gate, and aliasing re-run with fresh state.
# (Byte-level request replay would freeze a create-vs-update decision made blind.)
try:
index = idx_mod.load()
except echo.EchoError as exc:
if "unreachable" not in str(exc):
raise
if dry_run:
print("offline: vault unreachable — a real run would queue this capture "
"for replay on the next reachable session.", file=real_stdout)
return 0
import echo_queue
echo_queue.enqueue_capture({
"kind": kind, "title": title, "body_text": body_text, "status_v": status_v,
"aliases": aliases, "sources": sources, "tags": tags, "date": date,
"domain": domain, "inbox": False, "no_log": no_log,
"force": force, "merge_into": merge_into, "today": today_s,
})
if as_json:
env = echo_output.envelope("queued:capture",
{"kind": kind, "title": title, "queued": True})
print(json.dumps(env, ensure_ascii=False), file=real_stdout)
else:
print(f"queued (offline): capture {kind} '{title}' — will replay through "
"capture on the next reachable session", file=real_stdout)
return 0
match_slug, existing = idx_mod.resolve(index, title)
# --merge-into: the operator has already identified the canonical entity (e.g. after
# a duplicate-gate stop) — route this capture as an UPDATE to it, whatever the title.