Skip to content

Automations

An automation is what a column does when a task enters it, each time the task’s agent finishes a turn there, and when the task leaves. That is what turns a board into a pipeline with no typing between stages, and with a turn-end row the board can move a card on by itself. What a move does to the task’s session around these rows is on Columns. How they run beside a workflow, which moves its own tasks, is under Automations and workflows.

Each column owns one ordered list, split into three groups in that order: On task enter, On agent turn end, and On task exit. Each row has its own switch. A row belongs to one column; there is no sharing and no library. You edit the list in the Column Manager: click a column header, and the column’s automations are on the right.

The Column Manager on the Code Review column: Codex CLI on gpt-5.5 as the agent that starts there, and an Automations pane holding one Send message row on enter with the message it sends, edit and delete controls, and its switch on. v0.46.0 The real app on a sample install. The projects and terminals are recordings, connected to nothing, so click anything.

Five types:

TypeWhat it does
Send message to agentTypes a message at the column’s agent. Plain instructions or a slash command.
Run scriptRuns a script in the task’s worktree, or the project checkout when it has none.
Call webhookCalls a URL. Makes up to three attempts on a transport error, a 408, a 429, or a 5xx.
Notify meRaises one desktop notification. Clicking it opens the task.
Move to columnMoves the task to another column, with an optional message for the agent there. On agent turn end only.

Any row, of any type and on any trigger, can also carry a Condition that has to hold before it runs and a Frequency that caps how many times it runs on a task; see Condition and frequency.

A turn-end row runs when the task’s agent finishes a turn in its column. That is the agent’s own end of turn, reported by its CLI after any background work the turn started has settled. An interrupted turn, a failed one, and a terminal that has merely gone quiet are not turn ends. This is the trigger for the loops a board runs by itself: a review that sends its findings back to an earlier column, or a gate that moves a passing task on.

Only agents that report when a turn ends can run these rows: Claude Code, Gemini CLI, GitHub Copilot CLI, Grok Build, Antigravity CLI, OpenCode, Qwen Code, and Kimi Code. Codex, Cursor CLI, Goose CLI, Aider, Factory Droid, Warp (Oz CLI), and Ollama leave a quiet terminal as their only signal, which cannot tell a finished turn from a long build. On a column whose agent is one of those, the Column Manager shows the reason in place of the group’s Add automation control. It does the same on To Do, Done, and any column with Start an agent here off, where no agent ends a turn.

When a turn ends, Kangentic waits for the agent’s last output to land, then reads its final message once for every row that needs it. The rows then run by these rules:

  • Top to bottom, once per visit. Each row runs at most once while the task stays in the column. Only a row that actually runs counts, so a Condition that answers no is checked again at the next turn end.
  • A move ends the group. A Move to column row that runs moves the task out of the column, and the rows after it no longer apply.
  • Only a turn that began in this visit. A task dragged in while its agent was mid-turn ends that turn here, but the turn did the last column’s work, so its end runs nothing. A plan approval is the exception: the agent implements in the same turn it planned in, so that turn counts in the column the approval moved the task to, and its reply is the implementation’s.
  • Only the latest turn. If a new turn starts while the rows are being checked, the pass stops and runs nothing more.
  • Not the recap turn. When Kangentic starts an expired conversation over from a recap, the agent’s reply to the recap is a report for you, so its turn end runs nothing.
  • A quiet terminal is not required. If the agent is idle but its CLI keeps repainting a status line for its own background work, the rows still run after two minutes.

A review loop is two Move to column rows on one column’s turn end. The first sends the task back to an earlier column, with the agent’s reply as its message, when an Agent evaluation says the review found work. The second moves the task on when the agent’s last line is a Text match for the verdict line the column’s message asks for. The first row that runs ends the group, so a task sent back never also moves on, and a Frequency on the first row caps how many trips the loop makes. The figure below shows such a pair, with both Condition kinds side by side.

The Column Manager on Testing, where Claude Code runs on Sonnet 5. On agent turn end runs two rows in order: Send findings back to Executing, an Agent evaluation with a message, up to three times, then On to Merge, a Text match on PR: ready to merge, once. v0.46.0 The real app on a sample install. The projects and terminals are recordings, connected to nothing, so click anything.

A workflow moves its own tasks. When the agent on a workflow task finishes a turn and the column’s work is done, the workflow moves the task to the next column on its route, and from the route’s last column to Done. A workflow task needs no Move to column row to move along its route. Add one only to send the task somewhere its route does not go, such as back to an earlier column when a review finds work.

Each time the agent on a workflow task finishes a turn, the column’s automations and the workflow act in a fixed order:

  1. The column’s On agent turn end automations run first, top to bottom. If one moves the task, say back a column, that move wins.
  2. Then the workflow moves the task on, once its check finds the column’s work done. It does this on a column with no automations set up, too.

The workflow’s step reads where the task is after the rows have run, so it never moves a task twice. Each kind of row affects it this way:

  • A row that moved the task leaves the step nothing to do. A Move to column row set to Next column sends a workflow task to the next column on its route, so a row and the step agree on where “next” is.
  • A Send message to agent row starts the agent’s next turn, so this turn end gets no workflow decision. The turn that answers the message does.
  • A row whose Condition answered no did nothing, so it does not hold the step back.

A row runs at most once per visit, but the step runs at every turn end until the task moves. You can keep talking to the agent, and the turn that finishes the work is the one that counts. To keep a workflow task in a column until something is true, give the column a row that acts while it is not, such as a message telling the agent to wait for CI and check again.

One agent does the judging for both features. The Automation agent answers every Agent evaluation Condition on a row and every workflow task’s step. It also names tasks and Command Terminals (see Auto-naming). You choose that agent, its model, and its effort in Settings under Workflows, and Setting up the agents walks that tab. Nothing falls back to the project’s default agent. With none chosen:

  • A row that carries an Agent evaluation is skipped, with that reason recorded.
  • Every workflow column becomes one a person moves on.
  • Naming is off.

A Text match needs no agent at all. Every check is one headless, read-only call that reads the agent’s final message and answers yes or no, so a small, fast model is usually enough. Each one is a paid call, recorded with the project’s usage. At most two checks run at once across the app, Conditions and workflow steps together.

Every row, on every trigger, can carry a Condition and a Frequency, each switched on from its own card in the row’s edit dialog. Kangentic checks them in a fixed order: the Frequency first, then a Text match, then an Agent evaluation. A row past its limit is never put to the Automation agent, and a Text match row never reaches it at all. A Condition that does not hold is recorded as skipped with its reason, and a skip is not a run.

A Condition is one of two kinds, and a row holds one.

Plain words, such as “All tests pass”, that the Automation agent judges. With no Automation agent set up, the Condition card shows Set up an Automation agent with an Open Settings button in place of the field, and a row that already carries an Agent evaluation is skipped with that reason.

Kangentic asks about every pending Condition in a group in one headless, read-only call. The call carries the task’s title, description, labels, and column, any linked pull request with its state, and either the move (on enter and exit) or the agent’s final message (at a turn end). It reads only that final message, never the rest of the conversation, so a column that wants its agent’s verdict judged needs a message that has the agent end on it. Condition text takes template variables.

A Condition the reply left unanswered is asked once more, and counts as not met if it is still unanswered. On enter and exit the check runs inside the move, so it is capped at 60 seconds (the exit group’s shared budget on exit). A turn end holds no lock, so its check is not capped.

A pattern checked in code against the last line of the agent’s final message. No agent is called, so a Text match costs nothing and works with no Automation agent set up, and a column whose rows are all Text matches makes no call at all. It runs On agent turn end only, the one trigger with a final message; on the other two the option is shown disabled with the reason.

  • The line. Kangentic reads the last line that still has a word in it, so a closing code fence or a --- rule after the real last line is skipped. A line that appears only earlier in the message never matches.
  • What is ignored. Case, punctuation and symbols (markdown marks included), invisible characters such as a zero-width space, and runs of spaces. **Verdict: Ready**, Verdict: Ready., and - Verdict: Ready all match the pattern Verdict: Ready.
  • Wildcards. The pattern has to cover the whole line, and * matches any text: Ready for * matches a line that starts with it, * for review one that ends with it, and *review* one that contains it.

A miss is recorded as skipped with the line Kangentic read (“Text match not met: The final line reads …”), so the run says what the agent actually ended on. Reach for a Text match when the column’s message tells the agent to end on an exact line, and for an Agent evaluation when the verdict is a judgment no single line carries.

Maximum runs caps how many times a row runs on one task, counted across every visit to the column. A run its Condition skipped does not count. The count starts over when the task leaves Done, by a drag or a restore from the completed-tasks dialog, so a reopened task goes through the board’s loops as if for the first time. A row past its limit is skipped with the limit as its reason (“Already ran 3 times on this task, its limit.”), recorded once per visit, so a gate that stopped moving the task says why.

What each of the five types does when it runs, and the fields it adds to the edit dialog.

The most common automation. Set a Code Review column’s message to:

Review the code changes you just made. Check for bugs, security issues, and adherence to the project’s coding standards.

When you drag a task from Executing to Code Review, that prompt is sent to the running agent - no manual typing needed.

A message is free prose. A slash command works too, but nothing requires that shape: whatever you type is delivered verbatim as an ordinary user message in the running conversation. Messages take template variables, so they can reference the task’s title, branch, or worktree path.

A message needs an agent to type at. On enter, Kangentic starts the column’s agent right before the first automation that needs one, so nothing in the list has to start it. Turning Start an agent here off on a column also stops every Send message to agent row on it, since no agent will be there to receive it; those rows show as off with a disabled switch and the reason. On exit there is no agent to start, so a message row on a column whose task has no live session is skipped with that reason recorded. At a turn end the agent is the one that just finished, and the message starts its next turn.

Each message row chooses when it fires, with a Delivery control in its edit dialog:

SettingKeyBehavior
Run immediatelyimmediate (the default)Delivers as soon as the task arrives. If the agent is mid-turn the message is not cut off: it is typed and submitted, and the CLI queues it to run once the turn finishes. If a delivery is already in flight for that task, the new one queues behind it rather than being dropped
Wait for current turndeferredHolds the message Kangentic-side until the agent’s current turn genuinely completes, then delivers

Delivery is non-destructive in both modes. Nothing Kangentic sends interrupts a running turn: the prompt is cleared with Ctrl+U, a line-editing key, and the escape that dismisses a slash-command picker is never sent while a turn is live.

The difference is where the waiting happens. Run immediately hands the message straight to the agent and lets the CLI hold it in its own queue. Wait for current turn keeps it on Kangentic’s side until the turn has genuinely settled, so the message lands on a quiet prompt, which is what you want when it needs to act on the finished result rather than sit behind it. A deferred wait is capped, so a turn that never settles still delivers rather than hanging forever.

Because the choice belongs to the row rather than the column, one column can hold an immediate message and a deferred one.

A freshly spawned session is a third case and follows neither setting: delivery waits for the agent’s first activity, so the message does not land before the CLI is ready to read it. On agent turn end is a fourth: the turn is already over, so the message goes at once, and the dialog shows Delivery disabled with that reason.

A message with more than one line, or over 1,000 characters, is pasted rather than typed, so its line breaks survive: a {{description}} or a review’s {{agentReply}} arrives whole. A paste waits for the agent’s turn to end before it lands, whatever the Delivery setting, except an immediate message on exit, which goes at once. A message that opens with a slash command is always typed, because a pasted /code-review is plain text to the CLI and never runs; its line breaks become spaces. Nothing reads a paste back from the agent’s history, so it is reported as unconfirmed, like a typed message on an agent Kangentic cannot verify.

Injection is verified rather than fire-and-forget where the agent’s own history makes that possible. Kangentic reads the CLI’s session history back and looks for the text it just submitted, which is the difference between “we typed it” and “the agent took it as a turn”. A message that has not landed yet is retried on Enter, and that retry rung is what closes the delivery gap in practice.

Nine of the fifteen agents can be verified this way: Claude Code, Codex, GitHub Copilot CLI, Grok Build, Antigravity CLI, OpenCode, Aider, Qwen Code, and Kimi Code. Each reads its own agent’s format, so OpenCode is queried out of its SQLite store rather than a file, Aider is matched in the project’s chat-history markdown, and Claude matches its structured slash-invocation record.

Verifying and escalating are two different permissions, and most agents only have the first:

TierAgentsWhat a failed delivery does
Verify and escalateClaude Code, CodexRetries on Enter, then escalates once to restarting the session with the message passed as the CLI’s own prompt argument, which the spawn guarantees rather than TUI timing
Confirm onlyGitHub Copilot CLI, Grok Build, Antigravity CLI, OpenCode, Aider, Qwen Code, Kimi CodeRetries on Enter and reports the outcome. Never restarts the session
UnverifiedGemini CLI, Cursor CLI, Goose CLI, Factory Droid, Warp (Oz CLI), OllamaDelivered and recorded as unconfirmed. No retry rung, no escalation

The confirm-only tier is a deliberate floor, not an oversight. Escalation restarts a session and so destroys live work, which means it takes two separate proofs: that the CLI writes its history fast enough to be read inside the delivery window, and that this particular adapter’s resolver finds that record in a running app. A measurement alone does not establish the second, and a wrong path or an uncaptured session ID would turn every single delivery into a false failure and therefore a restart. Declining to escalate is the non-destructive answer, and the worst case for those seven is a reported failure and a notice.

Three of the unverified coding agents are a measured verdict rather than an unexplored gap: Gemini, Cursor, and Droid write their history at turn end, or too variably to bound, so nothing readable exists inside the window. Goose is unverified for a simpler reason: the first-pass adapter reads no Goose history, so there is nothing to check a submission against. Warp and Ollama expose no usable history at all.

One more case stays unconfirmed on purpose. If your message begins with /, Codex, Grok Build, Antigravity, and OpenCode decline to verify it, because absence from the history file is ambiguous there: the CLI may have rejected the command, or it may have run it entirely client-side (/status, /compact) so it never became a conversation turn. Those two need opposite responses and a verifier cannot tell them apart, so Kangentic neither retries nor escalates on a slash command for those agents.

Escalation is also narrower than the tier table alone suggests. It is wired for one delivery path: moving a task between columns while its session is already running. A freshly spawned session’s message is verified and retried the same way, but it stops at a reported failure on every agent rather than escalating. And escalation only happens after the turn-completion check passes, so it can never kill live work.

Either way the outcome is recorded on the task, and a delivery that could not be confirmed raises a notice instead of failing silently. The run log records a message as Sent on enter and Delivered on exit and at a turn end: an exit or turn-end message is awaited before the group goes on, while an enter message is handed to the scheduler and the move does not wait for it.

Runs a script as an ordinary child process, in the task’s worktree when it has one and the project checkout when it does not. Use it for setup or validation: npm run lint -- --fix before the agent starts, or npm test on the way into Merge.

Kangentic waits for the script to exit and records its exit code, so a non-zero exit is a recorded failure rather than a silent one. A script that runs past its timeout (10 minutes by default; see timeoutMinutes) is killed along with everything it spawned, not just the shell.

Template variables in the script body are shell-escaped, which strips metacharacters from the substituted text. Every variable also arrives as a KANGENTIC_* environment variable, named by upper-casing the variable and splitting its words with underscores ({{title}} is $KANGENTIC_TITLE, {{branchName}} is $KANGENTIC_BRANCH_NAME, {{task_xml}} is $KANGENTIC_TASK_XML), which is the way to read a value exactly, on every shell.

Sends an HTTP request to a URL: a Slack incoming webhook, a logging endpoint, or any external API. The fields are the URL, the method (POST, GET, or PUT), an optional body, and extra headers written one per line as Name: Value. Leave the body empty to send Kangentic’s default JSON payload: the event and trigger, the column names involved, the task’s id, number, title, labels, branch, and PR details, and the project’s id and name. The description is not in it; put {{description}} in a body of your own if a receiver needs it.

The call times out after 30 seconds and makes up to three attempts (the first call plus two retries) on a transport error, a 408, a 429, or a 5xx, honoring Retry-After. Every attempt carries an Idempotency-Key built from the run, so a retry cannot double-create anything downstream. Template variables in the URL are percent-encoded and those in the body are JSON-escaped.

To log every column move, put a Call webhook row on the On task exit group of each column, or on the enter and exit groups of the columns you care about.

Raises one desktop notification with a title (default {{title}}) and a body (default {{toColumn}}). Clicking it opens the task. Put one on a Merge column’s On task enter group and the board tells you when something reaches the finish line. On a turn end there is no move, so {{toColumn}} is empty there; give the body your own text.

Moves the task to another column when its agent finishes a turn. The Column field lists the board’s columns with Next column first: the column after the task’s own on the board, or, for a task in a workflow, the next column on its route. Done is a destination like any other, so Next column from the last column before Done archives the task. Renaming a column updates the rows that name it.

A task in a running workflow already moves along its route at each turn end without one of these rows; see Automations and workflows.

The move takes the same path as a drag: the source column’s exit rows, one rule from the move ladder, then the destination’s enter rows. A row that runs ends its group. It can move a task in a running workflow, though never into To Do.

The optional Message goes to the agent in the destination column once that agent’s turn ends, so it lands after that column’s own enter message, and it is pasted whole with its line breaks. Put {{agentReply}} in it and a review’s findings ride along to the column that fixes them. A destination with no agent running has nobody to hand it to, and the message is dropped.

It runs On agent turn end only, because on enter or exit its move would run inside the move that triggered it. Picking the type in the edit dialog moves the row to that trigger, and kangentic.json skips a Move to column row filed under any other.

Adding one. Each group has its own Add automation control, so where you add decides when it runs. The picker offers the types that group can run (Move to column appears under On agent turn end only), and below them every row on every other column, to copy. A copy is independent of the original. A type the column cannot run is offered disabled, with the reason.

Editing one. Click the row (or its pencil) to open the Edit automation dialog: a Name, then four cards. Trigger picks On task enter, On agent turn end, or On task exit, with any the column cannot run disabled and the reason on hover. Condition and Frequency each have a switch (see Condition and frequency). Action holds the Type and that type’s own fields. The grip, the switch, and the trash keep their own jobs and do not open it. A name is required and must be unique on its column. Every text field takes template variables: type {{ to pick one, or use the Template variable button. A known variable is highlighted; an unknown one is flagged and sent as written.

Reading a row. Each option that is on shows as a mark beside the row’s name: a robot for an Agent evaluation, a magnifier over text for a Text match, a repeat sign with the run limit for a Frequency, a clock for a message that waits for the current turn, and a speech bubble for a Move to column row’s message. Hover a mark for its value.

Reordering. Drag a row by its grip, or Tab to the grip and use Space and the arrow keys. A drag stays inside its own group: it changes where the row sits, never when it runs. To change when it runs, open the row and set its Trigger.

To Do and Done run exit automations only. Nothing runs when a task enters them, and no agent ends a turn there, so the Column Manager shows the reason in place of those two groups’ Add automation control.

Every run is recorded, whatever happens. Nothing shows on success. A failure raises one toast naming the automation and the column, with a Run again action that re-runs that one automation against the task’s current state. On a turn-end row whose Condition, Text match, or {{agentReply}} needs the agent’s final message, Run again reads the newest one the task’s session wrote. Nothing is retried automatically across a failure or a restart: a fired webhook and a half-run script are not safe to repeat blind. A run that was in flight when Kangentic quit is marked interrupted the next time the project opens, with one summary toast rather than one per row.

A skip is recorded too, with its reason: a Condition that answered no, a Text match that missed, or a spent Frequency. The run log itself is not shown in the Column Manager. The toast is the in-app answer to “did my automation work”, and an agent can read the full history, each Condition’s answer included, with kangentic_get_automation_runs.

Exit automations are capped at 60 seconds in aggregate, because they run while the task is locked and holding that lock is what would wedge the task’s next move. A row that would exceed the cap is recorded as failed. Exit is for quick handoffs; long work belongs on enter, which already holds the lock across the spawn and has a progress spinner to show for it. An exit automation never aborts the move: whatever happens in the group, the move proceeds.

Saving writes automations to kangentic.json under the column that owns them, so they are shared with your team through git; see Board configuration for the shape. The board header shows a lightning glyph with the count of automations that will actually run there, and All columns in the Column Manager shows every column’s counts side by side. Automations are shared by every Board profile: a profile changes what a column’s agent runs with, never what the column does.

Agents can read and write them too: kangentic_list_automations, kangentic_set_automations, and kangentic_run_automation.

Every automation field that takes text accepts these placeholders, and so does a per-task prompt set over MCP:

VariableReplaced with
{{task_xml}}Task title and description wrapped in an XML block. This leads the default spawn-prompt template ({{task_xml}}{{workflow_xml}}{{attachments}}); see What the agent is told for the rendered form
{{workflow_xml}}A workflow task’s <workflow> block: the workflow’s name, the task’s route, the tasks it builds on and the ones waiting on it, and how its turns are judged. Empty for a task outside a workflow
{{title}}Task title
{{description}}Task description with a : prefix when it is not empty, so {{title}}{{description}} reads as “Title: body”; an empty description renders nothing, prefix included
{{taskId}}Task UUID
{{taskNumber}}The task’s #N, the number shown on its card
{{projectPath}}Main project checkout path, always the base repository even when the task has a worktree (empty if no project is open)
{{projectName}}The project’s name as it reads in the sidebar
{{worktreePath}}Git worktree directory path (empty until the task has one)
{{branchName}}Git branch name (empty until the task has one)
{{baseBranch}}Effective base branch: the task’s own override, else the project’s configured default
{{prUrl}}URL of the linked pull request (empty until a PR is linked)
{{prNumber}}Number of the linked pull request (empty until a PR is linked)
{{prState}}State of the linked pull request: open, merged, closed, or draft (empty until a PR is linked)
{{issueKey}}The linked tracker issue’s key, such as a Jira key or a GitHub issue number (empty until the task is linked to one)
{{issueUrl}}The linked tracker issue’s URL (empty until the task is linked to one)
{{labels}}The task’s labels, comma-separated
{{attachments}}Attached file paths, one per line, appended after the task text; empty when the task has no attachments
{{port}}The lowest dev-server port this task has reserved (a task can hold up to 10; see kangentic_reserve_dev_ports). Usually empty: a task holds a port only once its agent has asked for one
{{column}}The column the automation belongs to. Automations only
{{fromColumn}}The column the task moved out of (empty for a task born into a column, and at a turn end). Automations only
{{toColumn}}The column the task moved into (empty at a turn end). Automations only
{{trigger}}When the automation runs: enter, turn_end, or exit. Automations only
{{agentReply}}The agent’s final message of the turn that just ended, in full with its line breaks. Filled On agent turn end only, and empty for an agent whose transcript Kangentic cannot read. Automations only

The last five describe a move or a turn, so the picker offers them only in an automation field. A spawn prompt is neither, and there they would always be empty. The Workflow agent’s first message has two variables of its own, {{columns}} and {{brief_xml}}, offered only there; see Workflows.

{{agentReply}} is read from the session whose turn ended, which for an isolated column is that column’s own session. On Claude Code it is read from where that turn began, so a turn that wrote no text of its own has an empty reply. Other agents read the newest reply in their transcript, which on such a turn can be an earlier turn’s.

Substitution follows two rules, and which one applies depends on where the template lives:

  • An automation field substitutes literally. A known variable is replaced; an unknown one is left exactly as written, which is what the editor promises when it flags the name and says it will be sent as written.
  • A per-task prompt set over MCP (kangentic_create_task’s autoCommand) drops an unknown or empty {{key}} and collapses the spaces around it, so /code-review {{baseBranch}} with no configured default yields /code-review rather than a trailing space. Nothing warns you about an empty keyword there, so dropping it is the kinder behavior; an automation field warns in the dialog before you save.
  • Columns - what a move does to the session, what the agent is told, and the Column Manager where you edit these rows
  • Workflows - linked tasks whose step runs after a column’s turn-end rows
  • Board configuration - the same rows as kangentic.json writes them
  • MCP tool reference - the tools an agent reads, writes, and runs automations with

Kangentic is free and open source. A star helps other people find it.

Star on GitHub