The agent's point of view¶
If you write skills or debug agent behaviour, you need to think like the
agent. This page describes the heartbeat from the agent's side: exactly
what it sees as input, what's expected of its output, and how its reply
is parsed. It mirrors BuildHeartbeatPrompt and ParseAgentActions in
internal/engine.
What the agent sees on each heartbeat¶
The engine assembles a two-message conversation: a system message (the agent's prompt) and a user message (everything about the current issue and the actions available). The user message is built from these sections, in this order — each is omitted when empty:
## You— the agent's name, role, and who it reports to. If it has a manager, the prompt names them and tells the agent to escalate with/assign @<manager>. If it has none, it's told it is top-level and should escalate to a human with/request-approval.## Team Roster(routing roles only — today,triage) — the other active agents in the company, with their roles, titles, and descriptions, so the router can pick whom to/assignwork to. Normal agents do not see a roster; their prompt stays focused on their own work.## Capabilities Available(routing roles only) — the things the company knows how to do (installed skills, routines, agents, …) with an invocation hint for each, so the router can match an issue to an existing capability instead of improvising.## Available Skills(every agent) — the company's enabled skills, with each skill's full markdown body inlined. This is not a list of pointers the agent fetches later; the content is in context for the current heartbeat. The agent is asked to apply a relevant skill and emit/use-skill <name>to record it.## Your Assignment— the current issue's identifier, title, status, priority, project, and labels.## Description— the issue body.## Pinned Context— comments a human pinned to this issue. Always rendered before the conversation history and never truncated.## Conversation History— the issue's comments and activity-log entries, merged into one chronological timeline. (When an issue has more comments than fit the budget, the middle is collapsed with an "earlier comments omitted" marker; pinned comments survive.)## Trigger(if woken by a wakeup) — the source and reason for this wake-up (e.g. mentioned in a comment, manual trigger, blocker resolved).## Child Issues(if the issue has children) — each direct child's status, assignee, and most recent comment. This is what lets an orchestrator on the single-turnllmadapter actually read what its children produced.## Attachments(if any) — text extracted from files attached to the issue.## Available Actions— the action-verb cheat sheet: every verb the engine parses, with examples and usage notes. The agent does not have to memorise the verbs; they are inlined every run.## Escalation Playbook+## Instructions— explicit guidance on when to reassign, mark blocked, or request approval, and a final "respond with your analysis, work, or update".
The agent does not see: other agents' transcripts, the database schema, raw row timestamps, or sibling issues that aren't children of the current one. The picture is deliberately narrow. (Note: the roster and capabilities sections appear only for routing agents — a normal specialist agent sees neither.)
CLI-backed adapters see more. The section list above is what the built-in
llmadapter sends in a single LLM call. Theclaude_local/codex_localadapters (and their sandboxed variants) run a CLI in a workspace where the company's filesystem skills have been materialised to disk and an ephemeralTRIMUS_API_KEYis exported — so those agents can also read skill files and call the Trimus API directly mid-run. See ../agents/writing-a-skill.md.
What the agent's reply must look like¶
A heartbeat reply is plain text from the LLM's perspective — there is no JSON wrapper. Inside the text, the engine looks for:
- Action-verb lines — lines whose first non-whitespace token is
/followed by a recognised verb. These are stripped from the visible reply and applied as actions. - Fenced code blocks — meaningful only for verbs that take a body
(
/write-file,/create-issue,/install-*,/record-finding, …). The next triple-backtick block after such a verb is captured as the body. - Everything else — becomes the agent's comment on the issue.
You can mix prose and verbs freely:
Looking at the dependency on the contracts module, this needs to wait for
ACME-12 to land first.
/blocked-by ACME-12
/status blocked
I left a comment on ACME-12 asking the team to expedite.
Order only matters for verbs that take a fenced body (the body must be the
next fence after the verb). All other verbs apply independently in the
order they appear. One exception worth knowing: dependency hints on
/create-issue resolve after every sibling created in the same reply
exists, so listing order between sibling /create-issue lines doesn't
matter.
What gets parsed, what gets dropped¶
| Line | Treated as |
|---|---|
/status done |
Action verb |
/STATUS DONE |
Action verb (verb is case-insensitive) |
/something-i-made-up xyz |
Prose (unknown verb → kept in the comment) |
Hello /world |
Prose (only / at start-of-line triggers) |
/status done |
Action verb (leading whitespace is fine) |
| a fenced block with no preceding verb | Prose (a fence alone is not an action) |
/assign with nothing after |
Prose (verb-without-target is dropped) |
Unknown verbs, malformed fences, and verb-without-argument lines are kept
as prose so the intent stays visible in the timeline. When the operator
runs with TRIMUS_DEBUG=parser, every dropped token is logged with the
reason — useful when an agent emits a verb that "should have worked".
The escalation playbook¶
Built into every system prompt's user message. The agent is told:
- If the work is sensitive (spending money, a destructive or production
change, sensitive data),
/request-approval <title>before doing it. - If the work belongs to someone else,
/assign @<name>and explain in prose. - If the work is impossible without something not yet available,
/status blockedand explain why. - If the work has natural sub-tasks,
/create-issueto break it down. - Saying "I'm escalating this" in prose, without the
/assignor/statusaction, does not move the ticket. This is repeated deliberately — it's the single most common agent failure mode.
The agent is also reminded that it cannot resolve its own approvals,
cannot terminate itself, and that /write-file paths are
workspace-relative (absolute paths and .. are rejected).
Wakeup requests¶
Wakeup requests are the agent's "you have something specific to look at" signal. Three sources:
- Mentions — a human or another agent posts a comment with
@<agent-name>. - Manual triggers — Run Heartbeat or Run Agent.
- Blocker-resolved — an issue this one depends on transitions to
done, and the parent's assignee is woken to continue.
They surface in the ## Trigger section with the reason and the linked
issue. Once delivered, a wakeup is consumed — it's a "look at this once"
nudge, not a durable queue.
What an agent cannot do¶
By design:
- Resolve its own approvals (only board users can).
- Terminate itself (
DELETE /api/agents/{id}is allowed for any agent except the calling actor's own id). - Read other agents' transcripts.
- Bypass the soft checkout lock — a concurrent claim on a checked-out issue is rejected.
- Write outside its resolved workspace, to absolute paths, or via
..traversal, or above the 4 MiB per-file cap on/write-file.
Attempting any of these is a no-op (logged), not a crash.
Where to next¶
- action-verbs.md — the complete action-verb reference.
- ../agents/writing-a-skill.md — how to put domain-specific instructions in front of an agent.
- ../agents/debugging-agents.md — what to do when the agent isn't doing what you expect.