feat(brief): generate .custodian-brief.md per repo for offline worker orientation
Adds _write_custodian_brief() to consistency_check.py. After every fix_repo() run, a .custodian-brief.md is written to the repo root with: domain, last-synced timestamp, current repo goal, active workstreams with progress (done/total), and the first 7 open tasks per workstream (blocked → in_progress → todo order) with task IDs. The file is git-committed when content changes so remote workers (e.g. CoulombCore) can pull it and orient without a live MCP connection. Session protocol template and CLAUDE.md updated: read .custodian-brief.md first, then call get_domain_summary() as an enhancement (skip if MCP unreachable). This eliminates false "State hub is offline" alarms in subagents and remote workers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,9 +84,11 @@ sudo service docker start
|
||||
Every Claude Code session in this repository must follow this ritual:
|
||||
|
||||
**On session start:**
|
||||
1. Call `get_state_summary()` via the `state-hub` MCP tool for orientation
|
||||
2. Check the agent inbox: `get_messages(to_agent="hub", unread_only=True)` — mark read and act on any messages
|
||||
3. Note any blocking decisions or blocked tasks before starting work
|
||||
1. Read `.custodian-brief.md` if it exists — offline-safe orientation that works without MCP
|
||||
2. Call `get_state_summary()` via the `state-hub` MCP tool for richer cross-domain context
|
||||
(if the MCP call fails, the brief is sufficient to begin work)
|
||||
3. Check the agent inbox: `get_messages(to_agent="hub", unread_only=True)` — mark read and act on any messages
|
||||
4. Note any blocking decisions or blocked tasks before starting work
|
||||
|
||||
**On session close (before ending):**
|
||||
1. Call `add_progress_event()` to log what was done, decided, or discovered
|
||||
|
||||
@@ -858,6 +858,143 @@ def _git_commit_writeback(
|
||||
return False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Worker orientation brief (.custodian-brief.md)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_BRIEF_HEADER = "<!-- custodian-brief: generated by fix-consistency — do not edit manually -->"
|
||||
_TASK_STATUS_ICON = {"done": "✓", "cancelled": "✗", "in_progress": "►", "blocked": "!", "todo": "·"}
|
||||
_OPEN_STATUSES = {"todo", "in_progress", "blocked"}
|
||||
|
||||
|
||||
def _write_custodian_brief(api_base: str, repo_slug: str, repo_path: str) -> bool:
|
||||
"""Generate .custodian-brief.md at the repo root and git-commit if changed.
|
||||
|
||||
The brief gives any agent — including subagents without MCP access and
|
||||
workers on remote machines — instant orientation without a live hub
|
||||
connection. Returns True if the file was written (content changed).
|
||||
"""
|
||||
import datetime as _dt
|
||||
from datetime import timezone as _tz
|
||||
|
||||
repo = _api_get(api_base, f"/repos/{repo_slug}")
|
||||
if not repo:
|
||||
return False
|
||||
|
||||
repo_id: str = repo.get("id", "")
|
||||
domain_slug: str = ""
|
||||
|
||||
# Resolve domain slug via the topic linked to any workstream
|
||||
workstreams = _api_get(api_base, "/workstreams", {"repo_id": repo_id, "status": "active"}) or []
|
||||
if isinstance(workstreams, list) and workstreams:
|
||||
topic = _api_get(api_base, f"/topics/{workstreams[0].get('topic_id', '')}")
|
||||
if topic:
|
||||
domain_slug = topic.get("domain_slug", "")
|
||||
|
||||
# Active repo goal (first active one if multiple)
|
||||
goal_text = ""
|
||||
goals = _api_get(api_base, "/repo-goals", {"repo_slug": repo_slug}) or []
|
||||
if isinstance(goals, list):
|
||||
active_goals = [g for g in goals if g.get("status") == "active"]
|
||||
if active_goals:
|
||||
g = active_goals[0]
|
||||
goal_text = g.get("title", "") or g.get("description", "")
|
||||
|
||||
now_utc = _dt.datetime.now(_tz.utc)
|
||||
ts = now_utc.strftime("%Y-%m-%d %H:%M UTC")
|
||||
|
||||
lines = [
|
||||
_BRIEF_HEADER,
|
||||
f"# Custodian Brief — {repo_slug}",
|
||||
"",
|
||||
f"**Domain:** {domain_slug or '(unknown)'} ",
|
||||
f"**Last synced:** {ts} ",
|
||||
"**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*",
|
||||
"",
|
||||
]
|
||||
|
||||
if goal_text:
|
||||
lines += ["## Current Goal", "", goal_text, ""]
|
||||
|
||||
if isinstance(workstreams, list) and workstreams:
|
||||
lines.append("## Active Workstreams")
|
||||
for ws in workstreams:
|
||||
ws_title = ws.get("title", ws.get("slug", "?"))
|
||||
ws_id = ws["id"]
|
||||
tasks = _api_get(api_base, "/tasks", {"workstream_id": ws_id}) or []
|
||||
if not isinstance(tasks, list):
|
||||
tasks = []
|
||||
|
||||
done = sum(1 for t in tasks if t.get("status") in ("done", "cancelled"))
|
||||
total = len(tasks)
|
||||
pct = f"{done}/{total}" if total else "no tasks"
|
||||
|
||||
open_tasks = [t for t in tasks if t.get("status") in _OPEN_STATUSES]
|
||||
# Show blocked first, then in_progress, then todo (cap at 5)
|
||||
priority_order = {"blocked": 0, "in_progress": 1, "todo": 2}
|
||||
open_tasks.sort(key=lambda t: priority_order.get(t.get("status", "todo"), 9))
|
||||
|
||||
lines += [
|
||||
"",
|
||||
f"### {ws_title}",
|
||||
f"Progress: {pct} done | workstream_id: `{ws_id}`",
|
||||
]
|
||||
|
||||
if open_tasks:
|
||||
lines.append("")
|
||||
lines.append("**Open tasks:**")
|
||||
for t in open_tasks[:7]:
|
||||
icon = _TASK_STATUS_ICON.get(t.get("status", "todo"), "·")
|
||||
title = t.get("title", t["id"])
|
||||
tid = t["id"]
|
||||
status = t.get("status", "")
|
||||
blocker = t.get("blocking_reason", "")
|
||||
task_line = f"- {icon} {title} `{tid[:8]}`"
|
||||
if status == "blocked" and blocker:
|
||||
task_line += f"\n *(blocked: {blocker})*"
|
||||
lines.append(task_line)
|
||||
if len(open_tasks) > 7:
|
||||
lines.append(f"- … and {len(open_tasks) - 7} more open tasks")
|
||||
else:
|
||||
lines += ["## Active Workstreams", "", "*(none — repo may need first-session setup)*"]
|
||||
|
||||
lines += [
|
||||
"",
|
||||
"---",
|
||||
"## MCP Orientation (when available)",
|
||||
"",
|
||||
"If the state-hub MCP server is reachable, call:",
|
||||
f"`get_domain_summary(\"{domain_slug}\")`",
|
||||
"This provides richer cross-domain context.",
|
||||
"If the MCP call fails, use this file as your orientation source.",
|
||||
]
|
||||
|
||||
content = "\n".join(lines) + "\n"
|
||||
|
||||
brief_path = Path(repo_path) / ".custodian-brief.md"
|
||||
existing = brief_path.read_text(encoding="utf-8") if brief_path.exists() else ""
|
||||
|
||||
# Strip the timestamp line before comparing to avoid spurious writes
|
||||
def _strip_ts(text: str) -> str:
|
||||
return "\n".join(
|
||||
ln for ln in text.splitlines()
|
||||
if not ln.startswith("**Last synced:**")
|
||||
)
|
||||
|
||||
if _strip_ts(content) == _strip_ts(existing):
|
||||
return False # no meaningful change
|
||||
|
||||
brief_path.write_text(content, encoding="utf-8")
|
||||
|
||||
# Commit the brief so remote workers can pull it
|
||||
_git_commit_writeback(
|
||||
repo_path,
|
||||
brief_path,
|
||||
[f"update .custodian-brief.md for {repo_slug}"],
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fix engine
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1094,6 +1231,12 @@ def fix_repo(
|
||||
now_iso = _dt.datetime.now(_tz.utc).isoformat()
|
||||
_api_patch(api_base, f"/repos/{repo_slug}/", {"last_state_synced_at": now_iso})
|
||||
|
||||
# Write the worker orientation brief (.custodian-brief.md)
|
||||
if repo_path:
|
||||
brief_written = _write_custodian_brief(api_base, repo_slug, repo_path)
|
||||
if brief_written:
|
||||
report.fixes_applied.append("brief: .custodian-brief.md updated")
|
||||
|
||||
return report
|
||||
|
||||
|
||||
|
||||
@@ -3,10 +3,16 @@
|
||||
State Hub: http://127.0.0.1:8000
|
||||
|
||||
**Step 1 — Orient**
|
||||
|
||||
Read the offline-safe brief first — it works without a live hub connection:
|
||||
```bash
|
||||
cat .custodian-brief.md
|
||||
```
|
||||
Then call the MCP tool for richer cross-domain context (skip if unreachable):
|
||||
```
|
||||
get_domain_summary("{DOMAIN}")
|
||||
```
|
||||
If offline: `cd ~/the-custodian/state-hub && make api`
|
||||
If the hub is offline: `cd ~/the-custodian/state-hub && make api`
|
||||
|
||||
**Step 2 — Check inbox**
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user