- Migration e2f3a4b5c6d7: add last_state_synced_at to managed_repos
- consistency_check.py: PATCH last_state_synced_at after fix run;
fix ~ treated as non-empty state_hub_task_id (C-03 vs C-11);
fix _inject_task_id_into_block skipping injection when field exists
with null value
- install_hooks.sh: idempotent post-commit hook installer for all
registered repos (make install-hooks REPO= / install-hooks-all)
- gitea_inventory.py: compare coulomb Gitea org against state-hub
registered repos — registered / unregistered / hub-only sections
- infra/README.md: document systemd user timer + crontab fallback
- systemd user timer: custodian-sync.{service,timer} runs
fix-consistency-all every 15 min (enabled)
- dashboard/src/repo-sync.md: Repo Sync Health page — sync age table,
unregistered Gitea repos, hub-only repos
- api/routers/repos.py: GET /repos/{slug}/dispatch endpoint returning
active goal, pending tasks per workstream, human interventions
- mcp_server/server.py: get_repo_dispatch() MCP tool
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.3 KiB
id, type, title, domain, repo, status, state_hub_workstream_id, created, updated
| id | type | title | domain | repo | status | state_hub_workstream_id | created | updated |
|---|---|---|---|---|---|---|---|---|
| CUST-WP-0014 | workplan | Repo Sync Automation & Gitea Inventory | custodian | the-custodian | done | 27ea80bd-76bf-44a7-b0ed-e09748d5390b | 2026-03-16 | 2026-03-16 |
CUST-WP-0014 — Repo Sync Automation & Gitea Inventory
Problem
When a repo agent completes work and commits, the state-hub does not automatically
learn about it. Task statuses in workplan .md files go unsynced until a human
manually runs make fix-consistency REPO=<slug>. This breaks the episodic memory
loop: future sessions see stale hub state and give wrong orientation.
In parallel, the custodian only tracks repos that have been manually registered.
All other repos living on Gitea (http://92.205.130.254:32166, org coulomb) are
invisible — no workplan tracking, no SBOM, no goal alignment.
Goal
- Automatic sync: after every commit in a registered repo, the state-hub learns about it within seconds — no agent discipline required.
- Gitea inventory: the hub knows about every repo on Gitea; unregistered repos are surfaced so they can be onboarded or explicitly marked out-of-scope.
- Sync timestamp: every registered repo carries a
last_state_synced_attimestamp so health dashboards can detect stale repos at a glance. - Dispatch endpoint (Tier 3): the hub can tell any repo what active workplan it should be working on and what tasks are pending — foundation for autonomous agent sessions.
Architecture
┌──────────────┐ post-commit hook ┌───────────────────────────┐
│ repo agent │ ──────────────────► │ fix-consistency REPO=x │
│ (any repo) │ │ → updates task statuses │
└──────────────┘ │ → sets last_state_synced │
└───────────────────────────┘
▲
┌──────────────────────────┐ cron (15 min) │
│ fix-consistency-all │ ─────────────────────┘ (belt & suspenders)
└──────────────────────────┘
┌─────────────────────────────┐
│ Gitea API (:32166/coulomb) │ ──► gitea_inventory.py ──► surface gaps
└─────────────────────────────┘
┌─────────────────────────────────────┐
│ GET /repos/{slug}/dispatch │ ──► active workplan + pending tasks
└─────────────────────────────────────┘ for autonomous agent sessions
Task: Add last_state_synced_at to managed_repos
id: CUST-WP-0014-T01
status: todo
priority: high
state_hub_task_id: "f35c86a9-d927-4543-9e74-ff32cadcc766"
Migration: add last_state_synced_at: DateTime (nullable) to managed_repos.
Update consistency_check.py to PATCH this field to utcnow() after every
successful --fix run via PATCH /repos/{slug}/ (add endpoint if missing).
Update ManagedRepoRead schema to include the field.
Acceptance: GET /repos/the-custodian/ shows last_state_synced_at non-null
after running make fix-consistency REPO=the-custodian.
Task: Git post-commit hook installer
id: CUST-WP-0014-T02
status: todo
priority: high
state_hub_task_id: "97c831d9-d915-4b77-9dd6-929ff24dfd5e"
Create state-hub/scripts/install_hooks.sh:
- Accepts
--repo <slug>or--all(iteratesGET /repos/) - Resolves repo path from slug (convention:
/home/worsch/<slug>or via alocal_pathfield — see T05) - Writes
.git/hooks/post-committhat calls:cd ~/the-custodian/state-hub && make fix-consistency REPO=<slug> - Idempotent: prepends block guarded by
# custodian-sync-hookmarker if hook already exists; skips if marker present - Makes hook executable
Add make install-hooks REPO=<slug> and make install-hooks-all Makefile targets.
Acceptance: commit in marki-docx → last_state_synced_at updates within 2s.
Task: Periodic cron sync (belt-and-suspenders)
id: CUST-WP-0014-T03
status: todo
priority: medium
state_hub_task_id: "06be1c0b-893b-4fbb-967c-9842ba59ffaa"
Add a cron entry (via systemd user timer or direct crontab) that runs:
cd ~/the-custodian/state-hub && make fix-consistency-all
every 15 minutes when the state-hub API is reachable. Use a guard:
curl -sf http://127.0.0.1:8000/state/health || exit 0
Document the timer setup in state-hub/infra/README.md (systemd user timer
preferred on WSL2 if systemd is available; otherwise crontab fallback).
Acceptance: after stopping all agents for 15 min and making a manual workplan
edit, last_state_synced_at updates without human intervention.
Task: Gitea repo discovery tool
id: CUST-WP-0014-T04
status: todo
priority: high
state_hub_task_id: "f05a04e4-10f3-4c41-a73f-057f0dea5126"
Create state-hub/scripts/gitea_inventory.py:
- Reads Gitea base URL + token from env (
GITEA_URL,GITEA_TOKEN) or.env - Calls
GET /api/v1/orgs/coulomb/repos?limit=50&page=N(paginate) - Also includes user repos if needed:
GET /api/v1/user/repos - Compares result against
GET /repos/from state-hub - Outputs three sections:
- Registered — in both (show
last_state_synced_at) - Unregistered — on Gitea but not in hub (candidate for onboarding)
- Hub-only — in hub but no matching Gitea remote (stale or local-only)
- Registered — in both (show
Add make gitea-inventory Makefile target.
Add GITEA_URL=http://92.205.130.254:32166 and GITEA_TOKEN= to .env.example.
Acceptance: running make gitea-inventory with a valid token prints a clear
three-section report.
Task: Dashboard — Repo Sync Health page
id: CUST-WP-0014-T05
status: todo
priority: medium
state_hub_task_id: "ceae2737-4762-49e5-ae41-9eca3ca79dda"
Add dashboard/src/repo-sync.md Observable page:
- Table: all registered repos,
last_state_synced_at(age in h/m), colour-coded (green < 1h, orange 1–24h, red > 24h or null) - Section: Gitea repos not yet registered (calls a new data loader that wraps
gitea_inventory.py --json) - Inline "Register" action placeholder (links to
make register-projectdocs)
Add to nav in observablehq.config.js.
Task: Dispatch endpoint
id: CUST-WP-0014-T06
status: todo
priority: low
state_hub_task_id: "86b646f3-a966-4ff4-9c9f-8684f1e81c54"
Add GET /repos/{slug}/dispatch router in api/routers/repos.py:
Response shape:
{
"repo_slug": "marki-docx",
"active_goal": { "id": "...", "title": "...", "description": "..." },
"active_workstreams": [
{
"id": "...",
"title": "...",
"workplan_file": "workplans/MRKD-WP-0001-level1-core.md",
"pending_tasks": [
{ "id": "...", "title": "...", "priority": "high", "needs_human": false }
]
}
],
"human_interventions": [...],
"last_state_synced_at": "2026-03-16T..."
}
workplan_file is derived from the workstream's slug field matched against
known workplan naming conventions — or stored explicitly (stretch: add
workplan_path column to workstreams).
This endpoint is the foundation for a cron-triggered autonomous agent session:
curl http://127.0.0.1:8000/repos/marki-docx/dispatch | \
claude --print "You are the marki-docx agent. $(cat -)"
MCP tool: get_repo_dispatch(repo_slug).
Milestones
| # | Milestone | Tasks |
|---|---|---|
| M1 | Sync timestamp live | T01 |
| M2 | Auto-sync on commit | T01, T02 |
| M3 | Belt-and-suspenders | T03 |
| M4 | Gitea inventory visible | T04, T05 |
| M5 | Dispatch endpoint ready | T06 |
Dependencies
- Consistency engine (CUST-WP-0008) — completed ✓
managed_repostable (v0.5) — live ✓
Out of Scope
- Autonomous agent scheduling (that builds on T06 but is a separate workplan)
- Gitea webhook integration (post-commit hook covers the same use case locally)
- Multi-user Gitea orgs beyond
coulomb