Skip to content

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:

  1. ## 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.
  2. ## 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 /assign work to. Normal agents do not see a roster; their prompt stays focused on their own work.
  3. ## 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.
  4. ## 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.
  5. ## Your Assignment — the current issue's identifier, title, status, priority, project, and labels.
  6. ## Description — the issue body.
  7. ## Pinned Context — comments a human pinned to this issue. Always rendered before the conversation history and never truncated.
  8. ## 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.)
  9. ## Trigger (if woken by a wakeup) — the source and reason for this wake-up (e.g. mentioned in a comment, manual trigger, blocker resolved).
  10. ## 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-turn llm adapter actually read what its children produced.
  11. ## Attachments (if any) — text extracted from files attached to the issue.
  12. ## 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.
  13. ## 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 llm adapter sends in a single LLM call. The claude_local / codex_local adapters (and their sandboxed variants) run a CLI in a workspace where the company's filesystem skills have been materialised to disk and an ephemeral TRIMUS_API_KEY is 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 blocked and explain why.
  • If the work has natural sub-tasks, /create-issue to break it down.
  • Saying "I'm escalating this" in prose, without the /assign or /status action, 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 triggersRun 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