Skip to content

How agents run: the heartbeat

Agents in Trimus don't watch issues continuously. They wake up on a schedule — the heartbeat — find one piece of work, do something, and go back to sleep. Each wake-up is a discrete, recorded run.

This page walks the loop end to end so the timing in the UI stops feeling mysterious. It mirrors Engine.ExecuteHeartbeatRun and executeWithAdapter in internal/engine.

The loop, step by step

  1. A run is created and claimed. A background worker pool polls a tasks table with SELECT FOR UPDATE SKIP LOCKED, so multiple workers can run agents concurrently without ever grabbing the same run twice. A periodic timer enqueues heartbeat tasks for active agents; manual triggers and wakeups enqueue them on demand.
  2. Fetch the agent. If the agent isn't active, the run fails fast (cheap, recorded). A paused agent never gets past this step.
  3. Find work. The engine picks one issue, in this order:
  4. a wakeup request that names a specific issue (a mention, a manual trigger, or a resolved blocker), else
  5. the agent's own assigned issues that are actionable (backlog / todo / in_progress) and not checked out by someone else, else
  6. for routing-role agents (today, triage) only: the oldest unassigned, actionable issue in the company.

If nothing matches, the run ends cleanly with summary "no actionable work" — this is normal and costs nothing. 4. Checkout. The engine takes the soft lock on the chosen issue. If another agent already holds it, the run ends with "checkout conflict" (no error) and the other run keeps going. 5. Assemble context and workspace. The engine resolves the company and agent home directories, materialises the company's skills onto disk, builds a merged skills directory (agent > company > instance), and mints an ephemeral per-run Trimus API key so any skill scripts the agent runs can call back into the Trimus API. The key is revoked when the run ends. 6. Think. The agent's adapter is invoked with the assembled prompt — system prompt + issue context + comments + activity + skills (+ roster and capabilities for routing roles) + the action-verb cheat sheet + the escalation playbook. Depending on the adapter, "think" is an LLM HTTP call, a local CLI subprocess, a container, or a remote worker. 7. Post the reply. The agent's prose (everything that isn't an action verb) is posted as a comment on the issue. 8. Apply actions. The engine parses the reply for action verbs and applies each — status changes, assignments, new child issues, approvals, file writes, and the rest. 9. Release. The issue is auto-released so others can pick it up next. 10. Record. The run row is finalised with the model, provider, tokens in/out, cost, a summary, and the full streamed transcript. Budget policies are evaluated; a breach may open a budget incident. 11. Sleep. The agent waits for its next tick.

Single-issue per run. A heartbeat works one issue. To make progress on five issues, an agent needs five runs (its own ticks, or wakeups, or manual triggers). This is why a busy agent benefits from a shorter interval, and why orchestrators fan work out to subagents or child issues rather than trying to do everything in one reply.

Three ways a heartbeat can start

  • Scheduled — the periodic tick. The normal case.
  • Manual (UI)Run Heartbeat on the agent's page, or Run Agent on an issue's properties panel. These enqueue a run immediately.
  • Wakeup request — another agent or a comment @mention, a resolved blocker, or a trigger queued a reason for this agent to wake sooner than its next tick.

What you see in the UI while a heartbeat runs

  • The agent's status pill flips to active.
  • The dashboard's Live Activity widget shows a row with the agent name, current model, and a streaming first-line preview (delivered over Server-Sent Events).
  • The issue's Live Transcript panel streams the agent's reply as it arrives.
  • When the run finishes, the issue's Timeline gets a new comment plus a discrete entry for every applied action; the agent's Runs tab gets a new row.

Heartbeat is per-agent, not global

Each agent has its own heartbeat_enabled toggle and heartbeat_interval_sec. Reasons to vary them:

  • Long intervals (e.g. 600s) for agents that mostly review other agents' work — saves on idle polling.
  • Short intervals (e.g. 30s) for agents that must react fast, like a triage agent.
  • Heartbeat off for agents you only want to run on demand. They still work — but only via manual triggers, wakeups, or being assigned work that another agent's run triggers.

"Why isn't my agent running?"

Most common causes, in rough order:

  1. Heartbeat is off. Check the agent's Configuration tab.
  2. Agent is paused. The status badge says paused.
  3. No actionable issue. Nothing is assigned to it, or every assigned issue is blocked / done / cancelled.
  4. Adapter / provider is broken. Click Test Adapter on the agent page. A bad provider key makes the run fail fast — the error appears on the Runs tab.
  5. Budget incident. A budget-policy breach on the company can suspend new heartbeats. Check Costs → Incidents.

See ../agents/debugging-agents.md for the full field guide.

Cost

Every heartbeat that calls an LLM costs money. Trimus records the bill per run, and the dashboard's Costs widget rolls those up by agent, model, and day. Set a budget policy on the company (or a per-company monthly cost budget) to get warned — or blocked — before a runaway agent burns the month's spend.