Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

How a Turn Works

A turn is what happens between your prompt and the moment there is nothing left for anyone to decide about it. Understanding its shape makes the rest of heph predictable, so this chapter lays it out once.

prompt ──► rounds ──► gates ──► review ──► apply ──► done
              ▲                    │
              └────── feedback ◄───┘  (a rejection or comment sends the
                                        model another round in the same turn)

Rounds

A round is one request to the model and its reply. The reply is text, tool calls, or both. Heph runs each tool call in order, through the permission engine, feeds the results back, and the next round begins. Rounds continue until the model stops with nothing more to do.

Two things end the rounds early. You can interrupt (Interrupting). Or the model can hit its output limit; heph doubles the limit and re-sends once if a tool call was cut off mid-input, and otherwise ends the turn there with the text it has.

There is a ceiling of 200 rounds per turn, so a model that never says it is done cannot spend without end. Real turns are a handful of rounds.

What a tool returns stays in the model's context for the rest of the turn and is sent again on every round after, so heph keeps reads small. view returns a file whole only up to 400 lines; a longer file comes back as its outline, the functions, types, impl blocks, classes, or headings it defines with the lines each spans, and the model reads the range or the one item it needs from there (outline is also a tool on its own). The outline is found from the text by pattern, so it is approximate; it covers Rust, Python, JavaScript and TypeScript, Go, Ruby, shell, the C-like languages, Markdown, Norg, TOML, YAML, and Makefiles, and a long file of another kind comes back as its first lines. Norg includes star headings and TODO list entries, even in a flat file like TODO.norg; view(symbol) uses the title without its marker or status extensions. Code/metadata, macro definitions, comments, and examples are skipped. The scanner is approximate and does not interpret every Norg construct. The system prompt tells the model to read this way.

The worktree

The model never works in your files. When a turn begins, heph fetches the conversation's upstream from your repository, the tip of the conversation's own branch once it has one and of its base branch until then (Branches), and replays this conversation's previously accepted private state on top of it, as a rebase would, then checks the result out in the conversation's worktree. What your checkout has checked out does not matter: switching branches there changes no conversation. Commits that appeared on the upstream since the last turn, whether you made them or another conversation published them, come in underneath the conversation's own change; they never show up as something for it to undo. Where a new commit and the conversation changed the same lines, the commit's text stands and the model is handed the displaced text to reapply, which then goes through review like any other edit. Dirty and untracked files in your checkout are not imported.

Every write the model makes is committed on a private shadow chain with its stated reason. Accepted state remains in this conversation across turns, which lets the agent build and test cumulatively without exposing unfinished changes to your checkout or to another conversation. Anything else the worktree holds when a turn begins, such as a file an interrupted turn wrote or one you edited there by hand, is carried onto the new base the same way and shown in that turn's review; nothing in the worktree is silently dropped or silently adopted.

Agent-run Git commands in the worktree use your source directory's configured user.name and user.email, including global config and conditional includes. Heph copies these into the shadow when it opens and refreshes them on reopening, so commits and rebases need no identity setup. Heph's internal snapshots keep their own heph <heph@localhost> identity. Hooks and signing settings are not copied from your repository.

Paths your repository ignores are not copied, so target/ and node_modules/ are not duplicated; the Worktree chapter says how to link them in when the model's commands need them. CARGO_TARGET_DIR points at one directory per project shared by every worktree, so a build in the worktree reuses your dependencies.

Markdown formatting

For named .md or .markdown files, the model can use markdown_format instead of running a formatter command. Heph embeds yucky and formats at 80 columns without spawning a process or generating HTML. Formatting changes enter normal review; check = true reports differences without writing. The result lists file statuses and parser warnings, not whole documents. Directories and symlinks are refused, and every named file is checked before any write.

The tool is Level 1 in both modes, so plan turns do not offer it. Design turns can format files within their configured design paths. See the tools reference for its parameters.

Gates

When the model stops, the acceptance gates run on what it changed: commands you configure, such as cargo test, run in the worktree. A gate that fails goes straight back to the model with its output; the model gets a fixed number of tries per gate before heph shows you the review with the failure attached. You never review something the gates could have caught.

Review

Then the turn's diff, everything the model changed since your snapshot, is presented for review, each hunk with the rationale the model gave for it. You accept, reject, or comment, hunk by hunk, in heph's own screen or in lazygit or another tool you configure.

What you accept is committed as the accepted state. What you reject is recorded as a constraint: the model is told, in your words, what was refused and why, and told not to propose it again in this turn.

Another round, or private acceptance

If you rejected or commented on anything, the model gets another round in the same turn, with your feedback. It edits again; the gates run again on what you kept plus what it changed; a second review shows only the new change. This continues until you accept everything or the model gives up.

If you accept everything, heph advances only the conversation's private snapshot. Your checkout is unchanged. The next turn starts from that snapshot plus any commits that have appeared upstream in the meantime.

When the turn ends with accepted changes and no pending review, heph prepares a publication candidate, passes its final gates, and offers to commit its drafted message in a dedicated dialog. y publishes immediately without an editor; e opens the message in your editor first. Neither needs another gate pass if upstream is unchanged; n or Esc leaves the work private. The publication lock is held while you answer. See Committing.

/commit is the explicit publication transaction: it integrates the private snapshot onto current committed HEAD, runs the due gates again on that exact tree, runs your hooks and signing, creates the commit, and only then updates the corresponding paths in your checkout. An overlap with a newer upstream commit stays private and must be resolved before publication.

What ends a turn

A turn ends when:

  • the model said it was done, the gates passed, and there was nothing to review or you accepted it all (turn done);

  • you interrupted it (turn interrupted); its work is still reviewable;

  • the model hit its output limit twice, declined the request, or filled its context window (turn ended: ...);

  • the model re-proposed a rejected change twice in one turn, at which point heph applies what you did accept and stops asking.

A turn that ends with changes you have not reviewed, because you were not there to review them, or because a gate kept failing, leaves them in the worktree and marks the conversation as waiting. Picking Up Where You Left Off covers what happens next.

What a turn costs

Every round is one API call, recorded with its token counts and cost at the model's frozen rates. The status bar shows day/session/thread totals; the end-of-turn line shows the turn's input/output/cache token and USD breakdown. Estimated usage is labeled; absent usage is unavailable rather than a zero bill. Every request is sent so that the provider's prompt cache covers as much of it as it can: the tool definitions, the system prompt, the whole conversation before this turn, and every round of this turn before the current one are marked for caching, so a round pays the input rate for its own new tokens and the cache-read rate (a tenth of it; heph models shows the rates side by side) for everything before them. The cache figure on the status bar is the share of prompt tokens served that way; in a long turn it climbs past 90%.

What a round carries is mostly tool results, and a result read in an earlier turn is rarely needed again. At the start of each turn heph replaces the results of the turns before it with one-line stubs, [view src/auth.rs: 412 lines, elided], so a long conversation's rounds stay about the size of the current turn. The model reads again what it needs. Context says what is kept and why the default keeps no earlier turn whole.