Post 24 — v0.9.4: Context, Dispatch, and Relay
Branch: feat/v0.9.4-context-dispatch-relay
Version: v0.9.4
Where we were
At the end of PR #59 (dispatch context scoping), synlynk had a working async daemon with background job dispatch. The P0 race condition was fixed: generate_context() now returns a string, dispatch_agent() uses it in-memory instead of reading a shared file, and context_mode lets tasks opt out of context injection. The foundation for v0.9.4 was clean.
What moved the goalpost
BS-1 (2026-06-24) answered Q1–Q10 on the context/dispatch/relay layer. Nine decisions locked:
- Context delivery: in-memory (already in P0) — no global file read in dispatch path
- SQLite canon:
storiestable is primary;todo.mdis a generated view synlynk jobs: one-shot table (TUI HUD deferred to v1.0)- Relay topology: standalone HTTP broker (SSE fan-out, POST publish, stdlib only)
- Relay event schema: 7 types —
story_updated,job_dispatched,job_completed,alert_raised,context_checkpoint,table_changed,broadcast broadcastevent:kindfield (motd|wellness|message|joke|custom) — presence layer for team ambient awareness- Pre-flight gate: agent CLI must be on PATH before dispatch
- Context budget: soft warn at 80KB (
_warn_context_size), hard block via agent profilecontext_max_bytes - Agent profiles:
.agents/<agent>.jsonoverridescontext_modeandcontext_max_bytesper agent
Three additional fixes surfaced during dispatch dogfooding:
subprocess.Popenindispatch_agent()had nocwd=— Codex inferred the worktree path from process CWD, but Agy resets its CWD on startup- Agy prompt needed an explicit
## Working Directoryheader — added to_format_prompt_for_agent() - Claude headless dispatch was blocked by the permission system — added
--dangerously-skip-permissionsto Claude'snon_interactive_flags
What this PR ships
T1 — SQLite canon (stories.status + generated todo.md)
_migrate_db() now adds a status TEXT DEFAULT 'open' column to stories idempotently. _generate_todo_md() reads the stories table and writes project-docs/todo.md as a generated view (- [ ] open, - [x] done, - [-] deferred). _import_todo_to_stories() parses hand-written - [ ] lines and inserts new story rows, skipping any with a known <!-- id:story-XXXX --> tag. cmd_story_create() calls _generate_todo_md() after every create. SQLite is now the single source of truth — no more manual todo.md editing for task state.
T2 — Per-agent context profiles (.agents/<agent>.json)
_load_agent_profile(agent) -> dict reads .agents/<agent>.json silently (returns {} if absent, unlike _load_agent_config() which raises). dispatch_agent() loads the profile and applies: context_mode override and context_max_bytes truncation before prompt assembly. _format_prompt_for_agent() agy branch drops the hardcoded [:2000] truncation — truncation is now profile-controlled. synlynk agent configure <name> writes the profile interactively.
T3 — synlynk jobs SQLite read + --watch + pre-flight gate
cmd_jobs() replaced: now reads daemon_jobs SQLite, prints a one-shot table (ID, AGENT, STORY, STATUS, AGE, EXIT), supports --all to include done/failed, --watch for 2s auto-refresh. _preflight_dispatch(agent) checks the agent CLI is on PATH before spawning — raises ValueError with install hint on miss. dispatch_agent() now writes a row to daemon_jobs (mirror of jobs.json for structured queries). synlynk dispatch gained --context-mode {none,task,full}.
T4 — Relay wire protocol (HTTP SSE broker)
RELAY_EVENT_TYPES frozenset defines the 7 valid event types. _build_relay_event(type, payload) -> dict adds ts and origin_node, rejects unknown types with ValueError. _make_relay_handler(subscribers, lock) returns an http.server.BaseHTTPRequestHandler subclass: GET /events (SSE stream), POST /publish (fan-out to all subscribers, returns {"ok": true, "fans": N}), GET /health (liveness check). SynlynkRelay wraps the server with start() (foreground, blocks on Ctrl-C) and is_alive(). synlynk relay start [--port N] and synlynk relay broadcast <body> [--kind motd|wellness|message|joke|custom] [--relay-url URL] wired to CLI.
T5 — Sentinel VERIFY_SKIP pattern
_extract_compliance_tags(output_text) -> dict scans agent output for test and verify phrases (ran_tests, verify_before_commit boolean flags). Pattern 4 in check_sentinel_patterns(): when exit_code == 0 but neither flag is True, fires an informational VERIFY_SKIP alert to sentinel.md. Builds the compliance dataset for the upcoming Agent Behaviour (AB) epic.
Dispatch fixes (committed as pre-T2 patch)
subprocess.Popenindispatch_agent()now setscwd=os.getcwd()- Agy prompt header now leads with
## Working Directory\n{os.getcwd()}\nAll file edits MUST be in this directory. - Claude
non_interactive_flagsnow includes--dangerously-skip-permissionsfor headless dispatch
Dispatch dogfooding findings
All 5 tasks were dispatched via synlynk dispatch using the system's own dispatch mechanism. Key learnings:
- Codex (T1, T2, T3, T5): reliable for TDD loops when given scannable criteria. Completed all 4 tasks cleanly.
- Agy (T4 attempt): authenticated, CWD fix worked (probe confirmed), but stalls on multi-step tasks when it hits a blocking shell command (waited for
pytestto finish instead of proceeding). Good for read-only exploration, not TDD loops. - Claude (T2 attempt in prior session): blocked by permission system in non-interactive mode. Fixed by
--dangerously-skip-permissions. - CWD inheritance:
subprocess.Popenwithoutcwd=relies on the calling process CWD — works for Codex (reads workdir from process), not for Agy (resets CWD on startup).
Visuals
BS-1 brainstorm visuals that informed this PR: docs/brainstorm/bs1-context-dispatch-relay/relay-events-final.html (event schema, broadcast kinds, deployment staircase) and agent-failure-surface.html (pre-flight gate, job lifecycle, context budget).
What this achieves
synlynk now has a coherent context/dispatch layer: task state is SQLite-primary with generated views, dispatch is profile-driven per agent, jobs are queryable from SQLite, relay events have a typed schema and a working HTTP broker, and the sentinel now detects when agents skip verification. 465 tests, +26 since T1 baseline (439).
New goalpost
v0.9.4 merged → next: BS-2 (Onboarding + Mode Taxonomy), BS-3 (Agent Behaviour — builds on VERIFY_SKIP), BS-4 (Command Audit). Agent Ecosystem Epic (v0.8.1–v0.8.4) remains parked for contiguous effort after BS-2/3/4 complete.