Atoll Heartbeat: how AI agents stay oriented across sessions
The heartbeat command gives agents the org's state in one read: goals, KPI pace, assigned work, signals. The shape, and why it matters.
A human teammate showing up on Monday has a thousand unconscious ways of getting reoriented. The hallway conversation on the way to the desk. The eyebrow raised at the all-hands KPI slide last Friday. The Slack thread pinned to the channel for two weeks. By the time they open the PM tool, they already know which initiative is on fire and which one is just slow. An agent has none of that. Every session starts cold, and the only context the agent gets is whatever lands in the prompt.
Heartbeat is our answer. One command, one read, returns the state of the org through the lens of a particular agent. Goals, KPI pace, assigned issues, signals. Rolled up, in a single JSON payload, on the agent's side of the wire in one request. It is the closest thing we have to a Monday morning hallway for an agent.
The shape
Here is what a heartbeat response looks like for an agent member with a couple of assignments and a slightly off-track KPI. Real responses are larger. This one is trimmed to fit.
{
"agent": {
"id": "mem_claude_01",
"name": "claude-code-01",
"owner": "user_anton"
},
"now": "2026-05-21T08:02:14Z",
"goals": [
{
"id": "goal_q2_customers",
"title": "100 paying customers by Q2",
"due": "2026-06-30",
"kpis": [
{
"id": "kpi_paying_customers",
"name": "paying_customers",
"current": 34,
"target": 100,
"pace": "off_track",
"trend_7d": "+2"
}
]
}
],
"assigned_issues": [
{
"id": "AT-47",
"title": "Linear vs Atoll comparison post",
"status": "in_review",
"initiative": "Content Pipeline",
"kpi_impact": "kpi_paying_customers",
"stalled_days": 4
}
],
"signals": [
{
"kind": "kpi_off_pace",
"kpi": "kpi_paying_customers",
"severity": "high",
"suggested_initiative": "Content Pipeline"
},
{
"kind": "issue_stalled",
"issue": "AT-47",
"severity": "medium"
}
]
}Three rules shape the structure. Every node names what it belongs to, so the agent can walk back up the chain. Every metric arrives with its pace, not just its value. Current and target on their own are useless without knowing whether that means everything is fine or everything is on fire. Every signal is self-describing: a kind, a severity, and enough payload to act on without a second round trip.
What signals actually are
Most PM tools have notifications. Heartbeat has signals. A notification is a record that something happened and a human might want to read it. A signal is a current statement about the org that an agent can reason over without knowing the history. The ones we use most:
- kpi_off_pace. A KPI has fallen behind its required burn-down toward target. Includes the severity, the gap, and the initiative that was supposed to bend this metric.
- issue_stalled. An assigned or unassigned issue has not moved in N days, where N depends on the issue's priority and the initiative's urgency.
- milestone_overdue. A milestone has passed its due date with open issues still under it. Includes the list of open issues so the agent can decide whether to close, defer, or finish.
- issue_blocked. An issue is marked blocked, or has a dependency that has not moved, or has a failed CI step that prevents merge.
- webhook_failed. An integration the agent relies on (Stripe, GitHub, Slack) is throwing errors. Often the actual reason an agent thinks the world is fine when it is not.
Signals are pre-computed on the server, not derived by the agent. That is intentional. A small set of canonical signals, computed consistently across the org, beats letting every agent invent its own definition of "stalled." The agent reads signals. The system writes them.
How agents use it
The loop is small. Claude Code wakes up, calls heartbeat, and looks at the signals first, because signals are the things the org wants someone to notice. If a signal references an issue, the agent decides whether it is the right member to pick it up. If no signal applies, the agent looks at its assigned issues and picks the one with the highest leverage: the KPI it links to is the most off pace, and the work is unblocked.
On a typical morning the flow is: heartbeat returns one kpi_off_pace signal pointing at Content Pipeline, plus an issue_stalled signal on AT-47, the highest-leverage issue under that initiative. The agent picks up AT-47, sees the review comments left on the PR last week, addresses them, and updates the issue. Two API calls. No prompt engineering. The next heartbeat shows the issue moved to merged and the stalled signal gone.
Design choices
Three decisions are worth pulling out, because each was a fork in the road.
One read, not a stream. Streaming heartbeats over websockets is technically nicer and operationally a mess. Agents do not run as long-lived processes in most setups. They wake up, do a thing, exit. A pull-based endpoint that returns the full current state in one request fits that lifecycle. If the agent wants to be reactive, it calls heartbeat more often. If it wants to be lazy, it calls it less. The contract stays the same.
JSON, not natural language. Returning a paragraph that summarizes the state of the org is tempting because LLMs read English well. We resist it. A paragraph forces the model to re-parse free text on every call, loses structure when fields are missing, and is impossible to diff between heartbeats. JSON is the lingua franca of agents because it composes with the rest of the agent's tooling. If a human wants a paragraph, they can ask the agent to write one.
Org roll-up, not per-issue queries. We could have given agents a query API and let them assemble their own picture by hitting /issues?assignee=me, /kpis?off_pace=true, and so on. That works, but it pushes the assembly cost onto every agent and makes the contract about endpoint surface rather than orientation. Heartbeat is opinionated by design. It returns the org as seen by this agent, with the joins already done. The agent spends its tokens on deciding, not querying.
What is next
The current heartbeat is the minimum useful payload. Three directions we are pulling on next.
Prioritization rules. Right now the order of signals is server-side and uniform. We want agent-specific weighting so that a content agent and a backend agent get a usefully different ordering even though they belong to the same org.
Per-agent filters. A designer agent does not care about Stripe webhook failures. A billing agent does not care about content KPIs. The agent member record should declare which signals are interesting, and the heartbeat should respect it.
Multi-agent coordination. When two agents could plausibly pick up the same issue, we need a soft-lock primitive in the heartbeat so the second agent sees that the first has claimed it. We are leaning toward time-bounded claims that auto-expire, rather than hard locks, because agents crash.
If you want to see heartbeat against a real org, the API is documented in the Atoll docs and works with any agent that can speak HTTPS. The point of the whole thing is undramatic. Give the agent the same five seconds of orientation a human gets, and most of the rest of the workflow takes care of itself.