Skip to content

feat: add agent-agnostic heartbeat orchestration API#358

Merged
joryirving merged 1 commit into
mainfrom
feat/357-agent-heartbeat-api
Jun 11, 2026
Merged

feat: add agent-agnostic heartbeat orchestration API#358
joryirving merged 1 commit into
mainfrom
feat/357-agent-heartbeat-api

Conversation

@itsmiso-ai

@itsmiso-ai itsmiso-ai commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds POST /api/agents/{agentName}/heartbeat — an agent-agnostic heartbeat orchestration endpoint that any agent can call to trigger a deterministic sync + reconcile pass.

What this PR adds

New endpoint: POST /api/agents/{agentName}/heartbeat

Authenticates via existing agent token flow, runs sync and reconciliation best-effort, records an AgentRun, and returns a compact result:

type AgentHeartbeatResponse = {
  status: "ok" | "warning" | "error";
  agentName: string;
  startedAt: string;
  finishedAt: string;
  summary: string;
  warnings: string[];
  errors: string[];
  touchedIssueUrls: string[];
};

Shared service helpers: src/lib/heartbeat.ts

  • runSyncBestEffort() — wraps existing syncIssuesForRepos with error aggregation
  • runReconcileBestEffort() — wraps existing reconcileClosedIssues with error aggregation

Both reuse Dispatch's existing service code (not shell scripts or local state).

Non-goals respected

  • No model/judgment grooming decisions in this endpoint
  • No Saffron-specific behavior or config
  • No dependency on workspace-local files or pod-local generated request JSON

Tests

16 tests covering:

  • Auth (401 without/with wrong token, valid bearer, agent-agnostic)
  • Success path (status ok, AgentRun recording, touchedIssueUrls, no grooming fields)
  • Warning aggregation (sync warnings, cross-phase aggregation, warning status in AgentRun)
  • Error response (sync errors, reconcile errors, both phases error, AgentRun recorded even on crash)

Files changed

File Description
src/app/api/agents/[agentName]/heartbeat/route.ts New endpoint (136 lines)
src/app/api/agents/[agentName]/heartbeat/route.test.ts Tests (290 lines)
src/lib/heartbeat.ts Shared service helpers (167 lines)

Fixes #357.

Add POST /api/agents/{agentName}/heartbeat endpoint that:
- Authenticates via existing agent token flow
- Runs deterministic sync/reconciliation using shared service code
- Aggregates warnings/errors (best-effort sync failures are warnings)
- Records an AgentRun for the heartbeat pass
- Returns compact machine-readable result with status ok|warning|error

Also adds src/lib/heartbeat.ts with runSyncBestEffort and
runReconcileBestEffort helpers that reuse existing issue-sync service
functions rather than shelling out to local scripts.

Includes 16 tests covering auth, success, warning aggregation, and
failure response behavior.

@its-saffron its-saffron Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

PR PR 358: feat: add agent-agnostic heartbeat orchestration API

Recommendation: Approve

This PR adds a well-designed heartbeat endpoint that follows existing repository patterns. The implementation is internally consistent, properly authenticated, and has comprehensive test coverage.

Change-by-Change Findings

src/app/api/agents/[agentName]/heartbeat/route.ts (new file, 123 lines)

  • Implements POST /api/agents/{agentName}/heartbeat with a clean orchestration pattern
  • Calls authorizeRequest at the top of the handler for authentication
  • Runs sync and reconciliation phases best-effort with error aggregation
  • Records AgentRun for auditability (best-effort, does not block on failure)
  • Returns structured AgentHeartbeatResponse with status, timestamps, warnings, errors, and touched issue URLs

src/lib/heartbeat.ts (new file, 175 lines)

  • Provides reusable runSyncBestEffort() and runReconcileBestEffort() helpers
  • Reuses existing service code from @/lib/issue-sync rather than duplicating logic
  • Aggregates per-repo results into warnings/errors with proper error handling (instanceof Error pattern)
  • Empty repo list returns an error (distinguishes "nothing to do" from "something went wrong")

src/app/api/agents/[agentName]/heartbeat/route.test.ts (new file, 412 lines)

  • 16 tests covering all specified scenarios: auth, success, warnings, errors, crash resilience
  • Verifies AgentRun recording across all status outcomes
  • Tests confirm agent-agnostic design works with any agentName
  • Tests verify no grooming/lane classification fields leak into response

Standards Compliance

No agent-specific names in generic docs — Uses generic agent/* pattern throughout
API routes return appropriate HTTP status codes — Returns 401 for auth failures, 200 for all other outcomes (errors surfaced in response body)
Error handling — Uses error instanceof Error pattern consistently
AuditLog integrationAgentRun records written for every heartbeat pass (best-effort, non-blocking)
Auth pattern — Uses authorizeRequest from @/lib/auth, matching all other agent API routes in the codebase
Secrets — No secrets committed; DISPATCH_AGENT_TOKEN is an env var reference


Required Checks

✅ verify route access controls are in place

  • The route calls authorizeRequest(request) at line 38, returning 401 if unauthorized
  • Tests confirm: no auth header → 401, wrong token → 401, valid Bearer → 200
  • The authorizeRequest function from @/lib/auth.ts enforces bearer token auth via DISPATCH_AGENT_TOKEN
  • Even if DISPATCH_AUTH_MODE=disabled is set (open operator UI), the explicit authorizeRequest check still enforces agent token auth for this route
  • Pattern matches other agent routes: /api/agent-runs, /api/agent-work/*, /api/agents/[agentName]/queue

✅ check for unintended public endpoints

  • No GET handler exported; only POST is defined
  • The DISPATCH_AUTH_MODE=disabled setting does not bypass the explicit authorizeRequest check in this handler
  • agentName path param is not used for authorization decisions — the same bearer token works for any agent name (agent-agnostic by design)
  • No rate limiting observed, but this is consistent with existing agent endpoints and not flagged in the repository standards

Evidence Provider Findings

No evidence provider findings configured.


Tool Harness Findings

✅ Verified src/app/api/agents/[agentName]/heartbeat/route.ts — confirms authorizeRequest check, best-effort phases, AgentRun recording
✅ Verified src/lib/heartbeat.ts — confirms reusable helpers, proper error aggregation, reuse of existing sync code
✅ Verified src/lib/auth.ts — confirms auth modes, bearer token handling, authorizeRequest implementation
git grep for DISPATCH_AGENT_TOKEN|isAuthorizedAgent|isAuthorizedBearer returned no matches in corpus (auth helpers are mocked in tests, verified via file reads)


Unknowns / Needs Verification

None — all required checks verified from the diff and supporting files.


Linked Issue Fit

No linked issue context was provided. The PR body states "Relates to PR 357" but no linked issue details were available for comparison against acceptance criteria. The PR description is self-contained and specifies non-goals (no model judgment, no Saffron-specific behavior, no workspace-local dependencies), all of which are respected in the implementation.

@joryirving joryirving merged commit 0716934 into main Jun 11, 2026
3 checks passed
@joryirving joryirving deleted the feat/357-agent-heartbeat-api branch June 11, 2026 23:26
@itsmiso-ai itsmiso-ai mentioned this pull request Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add agent-agnostic heartbeat orchestration API

2 participants