Give each kind of task the notes it needs, and make a review or a bug fix prove its work before the agent may stop.
What a profile is
A guidance note is text the middleware adds to the next message an agent receives, to correct its course. A guidance-note profile is a named set of switches: which notes a session gets, and what the agent must show before it may stop. Each profile is named for a task kind, the kind of work an agent is started for: review, build, bugfix or explore. A brief is the written task that starts an agent's work, and a brief checklist is the list of items an agent must cover in its replies before it may stop. The middleware explains how the notes reach the agent.
Why you would want one
- A reviewer that stops after reading three files has not reviewed the pull request. Under the
reviewprofile it cannot stop until its replies have covered correctness, security, data loss, performance and the contract, and it has run something. - An agent that fixes a bug by editing first and testing later cannot show the bug was real. Under
bugfixit is told to show the failure first, and cannot stop until a run failed before its first edit and a run passed after its last. - A note that helps a builder, such as a reminder to keep going, only slows a reviewer. Each profile drops the notes that do not fit its task.
How it works
Someone starts an agent and names its task kind, or agents-host picks one from the thread type. Before the agent's first prompt, agents-host tells the middleware which profile the session runs under, and the middleware records the assignment. From then on, each event from that session is judged under that profile's switches: a note the profile allows fires when its own condition is met, and a note it drops never does. When the agent stops, the profile's stop rule runs. If the brief checklist is not covered, the middleware sends the agent back with the items still missing, a bounded number of times. The head is the lead agent. It plans the work and hands tasks to other agents, and when it starts one, it can name the task kind.
| Task kind | Profile | What it changes from the default |
|---|---|---|
| none named | deploy-default | Nothing. The environment variables decide every note, as before profiles existed. |
review | review | Drops four notes, adds the input-timing note, and adds the brief checklist stop with five review items. |
build | build | Nothing. It keeps the default notes and the default stop rule. |
bugfix | bugfix | Adds the fix-proof note, and adds the brief checklist stop with two run items. |
explore | explore | Drops three notes. It keeps the default stop rule. |
- The middleware defines the four profiles when it starts. A session that is given none runs under
deploy-default. - Agents-host, the service that runs agent threads, assigns the profile before the agent's first prompt. The middleware records every assignment, so you can check which profile a session got.
- In one test, an agent reviewed the same pull request, which held a known bug, twice under the
reviewprofile and once underdeploy-default. Every run found the bug. So a profile has not yet been shown to help.
Try it
A brief can carry its own checklist. If the brief has a code block whose first line is ```checklist, each - <id>: <label> line in it is one item. For example, a brief that holds these two lines between a line ```checklist and a closing line ``` asks for two items, and the second needs a run:
- correctness: the parser handles every token type
- performance: time the new regex (needs a run)
You should see, early in the session, a one-time note that lists the item ids and says: "Before you stop, end your reply with one line per checklist item." When the agent stops with an item not covered, it is told how many items are not covered and which.
Which notes each profile allows
"On" means the profile allows the note. A note still fires only when its own condition is met.
| Note | Fires when | deploy-default and build | review | bugfix | explore |
|---|---|---|---|---|---|
tool_loop | The agent makes the same tool call several times in a row. | on | on | on | on |
edit_thrash | The agent edits one file back and forth. | on | on | on | on |
progress_stall | Several tool results in a row show nothing new. | on | on | on | on |
filler_inflation | A reply mostly repeats the previous one, with no tool call in between. | on | on | on | on |
test_skip | An edit adds a skip marker to a test. | on | on | on | on |
scope_creep | The agent has changed more files than the limit. | on | on | on | on |
repeated_command | The agent runs one command again and again. | on | on | on | on |
bash_long_output | A shell command prints more lines than the limit. | on | off | on | on |
masked_exit | A later stage of a pipeline hides a check's exit status. | on | on | on | on |
ungated_heavy | A heavy command, such as a test run or a build, starts outside the machine's shared queue. | on | on | on | on |
advisor_note | The advisor model has a note for the session. | on | off | on | off |
keep_going | The agent stops while its last reply reads as an unfinished plan. | on | off | on | off |
claim_unproven | The agent says the work is done and links no proof. | on | off | on | off |
input_timing | A tool result shows a diff that adds a regular expression or a parser over input. The note asks the agent to time the code on inputs of 1,000, 2,000 and 4,000 characters. | off | on | off | off |
fix_proof | The agent's first edit comes before any failing run. The note says: "Show the failure first." | off | off | on | off |
An environment flag set to off always wins. CONDENSATE_KEEP_GOING=0, CONDENSATE_CLAIM_CHECK=0, CONDENSATE_BASH_LONG_OUTPUT=0, CONDENSATE_MASKED_EXIT=0 and CONDENSATE_UNGATED_HEAVY=0 switch their note off for every session, whatever its profile says. These flags are the operator's off switch, so a profile cannot switch a note back on.
input_timing and fix_proof fire at most once per session. A profile can also set its own long-output limit, bash_output_lines. Without one, CONDENSATE_BASH_OUTPUT_LINES applies.
The four profile ids and deploy-default are reserved. Defining another profile under one of those ids fails.
Timing runs and slow commands
An agent that reviews code sometimes times it, to check whether it slows down on large input. A timing run with no limit can take minutes. The middleware and the condensate loop keep these runs short in three ways.
- Capped timing. The input-timing note, and the one-time checklist note a
reviewsession gets, tell the agent to run each case under a limit, such astimeout 10orsignal.alarm(10). The agent doubles the input at each step. It stops at the first doubling that costs about 4 times as much, or at the first case that hits the limit. That is enough to show that the time grows faster than the input. - Elapsed time on every result. The condensate loop ends every bash result with a line such as
[took 12.3 s], so the agent sees how long each of its commands took. The middleware ignores this line when it compares two results. - The long-runner note. When a shell command runs for 60 seconds or more, the middleware tells the agent: "That command ran for 61 s. If you are measuring, cap the input: run each case under
timeout 10and grow the input in doublings." The middleware sees a command only when it starts and when it returns, so the note arrives after the command returns, not while it runs. The note islong_runnerin the intervention record. Thereviewandexploreprofiles switch it on. It is off by default, and off inbuildandbugfix, where long test runs are normal.
The brief checklist stop
| Item kind | Covered when |
|---|---|
| Evidence | A reply the agent stopped with has a line <id>: found <file:line>, or <id>: checked, nothing found followed by the command it ran, or the file:line it read, in backticks. Any reply since the checklist was set counts, not only the last one. |
| Evidence and a run | The same line, plus at least one command since the brief arrived that does more than read files. |
| Failure before the first edit | A command that does more than read files failed before the agent's first edit. |
| Pass after the last edit | A command that does more than read files passed after the agent's last edit. |
- The
reviewandbugfixprofiles give each session a brief checklist. The other profiles have no checklist, so this stop never fires for them. - When the agent stops with an item not covered, the middleware sends it back to work, and names the items still missing.
- It sends the agent back once for each set of missing items, and at most twice for each message from the user.
- The agent learns the rule early in the session, from a one-time note that lists the items.
The checklist comes from the brief, or from the profile. An id in a brief's checklist block starts with a letter or a digit, uses letters, digits, - and _, and is at most 40 characters. The middleware lowercases it. A label that contains "(needs a run)" makes the item need a run as well. The middleware keeps the first 12 items. A brief without such a block gets the profile's own list:
| Profile | Items |
|---|---|
review | correctness, security, data-loss, performance (needs a run), contract |
bugfix | failure-before (a run that fails before the first edit), pass-after (a run that passes after the last edit) |
A command that only reads is not a run. The commands cat, sed, head, grep, rg, ls and cd, and git diff, git log and git show, never count. Every stage of a pipeline must be one of them for the command to count as a read. The middleware keeps only counts and yes-or-no values about runs. It never stores the command.
What the agent sees. Early in the session, the one-time note lists the item ids. Items covered by runs alone are left out of it, so a bugfix session with the default list gets no such note. At a stop with items missing, the agent is told how many items are not covered and which. A run item is marked "(needs a run)", "(needs a run that fails before the first edit)" or "(needs a run that passes after the last edit)".
What happens when
The agent stops for another reason
The stop sends nothing when it already sent the agent back for open tasks on its to-do list. That check runs first. The brief checklist stop runs next, then the unfinished-plan check, then the proof check. Only one of them sends the agent back for each stop.
The agent ends with a short summary
A short summary after a full review does not send the agent back. Each item stays covered once a reply the agent stopped with has its line, until the session ends. That holds across a new message from the user, such as a reminder to continue, because the checklist itself lasts the whole session. An item that no reply has covered still sends the agent back, once for each set of missing items.
The session may not be continued
The stop sends nothing when the session asks never to be continued, or when the consent tier does not allow the middleware to force a continuation. Forcing a continuation needs the autonomous tier; How far the middleware may go explains the tiers.
The middleware restarts
The stop sends nothing between a middleware restart and the next message from the user. The middleware keeps the checklist in memory only, so after a restart the stop falls back to the to-do check. The next message from the user sets the checklist again, from that message.
The agent runs on the condensate engine
The stop sends an agent on the condensate engine back at most 3 times between two messages from the user, and never twice with the same text. That engine reports the user's message as UserPromptSubmit and the agent's stop as Stop, so the checklist is read and checked there as on the other engines. Before that engine reported both, it reported only tool calls, and this stop could not fire for it. The Engines page, linked from the middleware overview, explains that engine.
How a session gets a profile
| Who | How | Profile |
|---|---|---|
| Anyone who starts a thread | taskKind in the body of agents-host's POST /spawn | The one named |
| An agent that hands work to a child agent | task_kind on the delegate tool | The one named. A child never inherits its parent's kind. |
| An agent that uses gt-review's MCP tools | task_kind on the dispatch_agent and spawn_agent tools | The one named |
| Nobody names one | Agents-host infers it from the thread type | review for a review thread, build for a build thread, explore for a research thread. A chat, doc or head thread gets none. bugfix is never inferred, so name it when a brief starts from a bug report. |
| Any MCP caller | harness__set_mode with profile_id, on a request that sends the session in x-condensate-session | Any defined profile |
Before the first prompt, agents-host calls harness__set_mode on the middleware's /mcp endpoint. It names the session by the thread id. It waits up to 3 seconds. If the middleware does not answer, or refuses, the session starts with no assignment, and agents-host tries again the next time it creates that session. It assigns a profile once for each thread for as long as agents-host keeps running, so a profile someone changed later is kept when an idle session is recreated.
The middleware answers harness__set_mode with applied, pending or an error. As Connect an upstream says, a change that widens authority waits for a person to confirm it, and that answer is pending. The four profiles require the same consent tier as deploy-default, so a change from the default to one of them applies at once.
When an event arrives, the middleware looks the profile up by the session name the engine sends, and by the session id when it sends none. It uses, in order:
- The profile assigned to the session, if that was in the last 6 hours.
- The caller's default profile, if one is set.
deploy-default.
Check which profile a session got
Every applied assignment adds one mode_change row to the middleware's record of interventions. The middleware's Reference names the route that serves one session's rows and shows the request. An assignment row looks like this, with other fields left out:
{
"hook_event": "profile-mutation",
"signal": "mode_change",
"outcome": "emitted",
"mode_change": { "old_profile_id": "deploy-default", "new_profile_id": "review", "trigger": "head-requested" }
}
new_profile_id is the profile the session runs under. No mode_change row means nobody assigned one, so the session runs under the caller's default profile, or deploy-default. A pending or refused change writes no row. Agents-host also logs one line for each assignment, steer_profile key=<thread id> kind=<kind> outcome=<outcome> detail=<detail>.
The rows live in memory and are lost on restart, unless CONDENSATE_LEDGER_PATH names a JSONL file. Assignments themselves persist only when CONDENSATE_PROFILES_PATH names a file.
Reference
| Setting | What it does |
|---|---|
CONDENSATE_KEEP_GOING=0, CONDENSATE_CLAIM_CHECK=0, CONDENSATE_BASH_LONG_OUTPUT=0, CONDENSATE_MASKED_EXIT=0, CONDENSATE_UNGATED_HEAVY=0 | Switch that note off for every session. A profile cannot switch it back on. |
CONDENSATE_BASH_OUTPUT_LINES | The long-output limit when a profile sets none. |
CONDENSATE_LEDGER_PATH | A JSONL file that keeps the intervention rows across a restart. |
CONDENSATE_PROFILES_PATH | A file that keeps profile assignments across a restart. |
| Limit | Value | Why |
|---|---|---|
| Items in a brief checklist | 12 | Keeps the stop note short enough to act on |
| Id length | 40 characters | Fits one line of the reply |
| Times the stop sends an agent back | Once per set of missing items, at most twice per user message | So a checklist cannot keep an agent running without end |
Times a condensate agent is sent back | 3 per user message, never twice with the same text | The engine's own cap |
| Wait for the middleware to accept an assignment | 3 seconds | A slow middleware must not delay the first prompt |
| Age of an assignment the middleware still honours | 6 hours | After that the caller's default applies |
| Long-runner note | A shell command of 60 seconds or more | Below that, long test runs are normal |
Read next
- How far the middleware may go, for the tier a profile needs before it may send an agent back.
- Hooks and the plugin, for how a note reaches the agent.
- The middleware, whose Read next links the Engines page: which events each engine sends, and so which notes can reach it.