Blog 18 — PR #52: v0.8.0 Support Engineer Agent
Series: Building toward full autonomous multi-agent dispatch
PR: #52 — feat/v0.8.0-support-engineer-agent
Date: 2026-06-21
Where we left off (PR #51)
PR #51 solved the Codex headless dispatch problem: codex exec - reads a prompt from stdin without requiring a TTY, and -s workspace-write confines writes to the workdir. The key lesson was that --dangerously-bypass-approvals-and-sandbox silently overrides -s entirely — caught by both AGY and Codex in a dual-agent review. The goalpost after PR #51: get an autonomous monitoring agent running so synlynk watches itself.
Strategic shift: Definition B decomposed
The original Definition B spec described three agents — Support Engineer, PM Agent, and Marketing Intern — as a single milestone. AGY's gap analysis (committed as docs/proposals/autopilot-gap-analysis-agy.md) raised five unresolved questions about ownership models, HITL thresholds, and cold-start logistics. Rather than spec all three at once, we scoped down to Support Engineer only. PM Agent and Marketing Intern remain as .agents/pm.yaml / .agents/marketing.yaml files to create later — no engine changes needed to add them.
What shipped
New subcommand: synlynk agent run <name>
synlynk agent run support # collect signals, investigate, file issues, attempt fixes
synlynk agent run support --dry-run # print findings only, no dispatch or GitHub calls
synlynk agent run support --install-cron # idempotent crontab install (0 */6 * * *)
synlynk agent list # show .agents/*.json files and last run timestamp
Agent config: .agents/support.json
Agents are defined by a JSON file, not code. The engine reads it; adding PM Agent later = a new .agents/pm.json, no engine changes:
{
"name": "support-engineer",
"investigator": "claude",
"fixer": "claude",
"signals": [
{"type": "test_suite", "command": "pytest tests/ -q --tb=short"},
{"type": "sentinel_alerts", "path": ".synlynk/sentinel.md"},
{"type": "telemetry_anomaly", "failure_rate_threshold": 0.30},
{"type": "capability_drop", "drop_threshold": 1.5},
{"type": "github_issues", "labels": ["bug", "needs-triage"]}
],
"hitl": {"auto_merge": false}
}
auto_merge: false is B-mode — draft PRs only. Flipping to true upgrades to C-mode without any code change.
5 signal collectors
| Signal | Method | Severity |
|---|---|---|
test_suite |
subprocess.run(["pytest", ...]) |
Any failure → high |
sentinel_alerts |
Read .synlynk/sentinel.md for ⚠ lines |
FLATLINE/QUOTA → high, others → medium |
telemetry_anomaly |
Last 20 telemetry entries, compute failure rate | >60% → high, >30% → medium |
capability_drop |
7-day vs prior-7-day avg quality per agent | Δ>3pts → high, Δ>1.5pts → medium |
github_issues |
gh issue list --label bug,needs-triage --json |
Always medium |
CI detection: GITHUB_ACTIONS=true skips sentinel_alerts and telemetry_anomaly (which require local .synlynk/ state).
7-day dedup window (autopilot_runs table)
New SQLite table prevents re-investigating the same signal within 7 days (30 days for github_issues, to avoid re-filing duplicate issues on persistent open bugs). signal_hash = MD5 of the finding's key content.
Investigation pipeline
For each new finding (not in dedup window):
- Create a story in
state.dbwith deterministic IDsupport-<hash[:8]> - Build prompt: signal detail + project context from
generate_context()+ task instruction - Run investigator foreground via
subprocess.run()(300s timeout) — not the backgrounddispatch_agent() - Detect fix signal:
# FIX:marker or--- a/unified diff in log output - File GitHub issue via
gh issue create --label bug,support-engineer - If fix signal: extract diff,
git apply, run tests, open draft PR or post failure comment - Write
autopilot_runsrow with status + PR URL
Engine caps at 5 findings per run (highest severity first).
Key implementation decisions
JSON not YAML for config files: synlynk is stdlib-only. Python 3 has no built-in YAML parser. JSON is equivalent for this config structure.
subprocess.run foreground, not dispatch_agent: dispatch_agent() uses Popen with start_new_session=True (background, fire-and-forget). The investigation pipeline needs to block and read the output before proceeding to file an issue. Building the same shell command pattern with subprocess.run(timeout=300) was the cleanest path without modifying dispatch_agent.
_attempt_fix returns (status, pr_url) tuple: The final code review caught that pr_url was always NULL in autopilot_runs. Changed return type to tuple so the PR URL from gh pr create is captured and stored.
Temp patch file cleanup: NamedTemporaryFile(delete=False) + git apply is wrapped in try/finally with os.unlink(patch_path) to prevent accumulation in /tmp/.
GitHub Actions trigger
.github/workflows/support-engineer.yml fires on push to main and on a 0 */6 * * * schedule. Requires ANTHROPIC_API_KEY and uses GITHUB_TOKEN (auto-provided) for issue/PR creation.
Tests: 200 passing (up from 176)
24 new tests cover every signal collector, dedup windows, the investigation dispatch, issue filing, diff extraction, fix attempt, engine orchestration (including dry-run no-side-effects), and cron install idempotency.
What was achieved
synlynk now watches itself. On every push to main (and every 6 hours), the Support Engineer collects health signals across 5 dimensions, deduplicates against a 7/30-day history, dispatches Claude to investigate new findings, files GitHub issues with investigation results, and opens draft fix PRs when the investigation produces a diff that passes the test suite.
The agent config file pattern means adding PM Agent or Marketing Intern = a new .agents/*.json file. The engine routes to them automatically.
New goalpost
v0.9.0: Support Engineer running stably in CI for at least one full week. Once confirmed, write .agents/pm.json for the PM Agent (growth-signal collector: star velocity, clone counts, install rate).