Skip to main content
Akumi Chat only. A conversation is not bounded by how much you can send it; it is bounded by how much the model you picked can read at once. Compaction is what keeps a conversation going once it outgrows that: the platform summarises the oldest part of the conversation and replaces it, in the prompt, with the summary. The conversation keeps working, on the same model, with nothing for anyone to configure. It happens on its own. Every turn, before the prompt is built, the platform checks whether the next request would fit: inside the model’s context window, and inside what that model will serve in one request, wherever its provider publishes that. When it would not, the oldest messages are summarised in one extra model call, and the turn you sent carries on from there. Most conversations never reach this: it exists for the ones that run long, hold large documents, or come back to every day for months. The second half of that check matters more than it sounds. A model’s window is usually larger than the biggest request its provider will accept at once, so a conversation can sit comfortably inside its window and still be too large to send. Measuring against the window alone left exactly that conversation behind: it looked finished, and answered a capacity refusal on the next turn. The threshold is whichever of the two is tighter, so a compacted conversation is one the model can actually be asked.

The transcript is untouched

This is the part worth being precise about. Compaction cuts what the model is shown, never what a person reads. Scroll to the top of a conversation that has been summarised three times and every message you or your model ever sent is still there, in order, exactly as it was written. Nothing is deleted and nothing is rewritten in place. What changes is the next prompt. From the cut point back, the model reads a summary instead of the messages themselves; from the cut point forward, it reads the messages verbatim, same as always. The two views (what a person sees, what the model is sent) are allowed to diverge, and compaction is the one thing in this platform that deliberately makes them diverge.

It is billed as ordinary inference

Summarising a conversation is itself a call to a model, on the conversation’s own model: a cheaper pinned model would send your conversation to a sub-processor your workspace never chose. That call is metered and billed exactly like the turn you sent, no separate rate, no surcharge for the feature. It appears on your usage page as its own line, so a conversation that has been summarised several times shows what each pass cost rather than folding it into the turn that happened to trigger it.

Where the summary begins

GET /chat/v1/threads/{thread} carries a compaction field on every read:
first_kept_message_uuid is the first message in the transcript still sent to the model verbatim; the summary stands in for everything before it. created_at is when that summary was written. A client draws one line above that message, the same way it would draw any other marker in the transcript: everything above the line is history a person can still read but the model no longer sees whole, everything below it is exactly what the model was sent. The field is null on a conversation that has never been compacted, never absent: a client reads it the same way on every conversation without first checking whether this one happens to have a summary. first_kept_message_uuid can itself be null inside a non-null compaction. The message it names is not kept forever just because a summary points at it, and a conversation’s normal retention can remove it later. When that happens the marker has nowhere left to point: render it as “this conversation was summarised” without the line, because the message the summary resumes at is gone.

When even the summary does not fit

This is the automatic case: a turn compacting on its own, inside a run, because it crossed the threshold on its own. A turn that cannot fit inside the model’s window, even after compacting, fails with context_exhausted. There is nothing left to cut on that model; the remedy is to start a new conversation, or pick a model with a larger window. The summarising call this automatic path makes can fail too, independently, and its failures carry the same word every other refusal on that turn would, with a compaction_ prefix, so a client can tell that the summariser was refused rather than the turn itself: compaction_context_exhausted when what is being summarised does not fit even in the summariser’s own call, compaction_rate_limit_error when the service is at capacity and the summarising call could not be made, compaction_pii_blocked when your workspace refuses a kind of personal data the messages being summarised carry, compaction_empty_summary when the model answered with nothing at all, and compaction_summary_refused when the prompt-injection check refused the summary the model wrote. The last two leave the conversation exactly as it was: nothing is summarised and nothing is cut, so the next turn is free to try again. Any other pipeline refusal reaching that call (an exhausted balance, a provider fault) is prefixed the same way, for the same reason: a bare code here would be indistinguishable from the same refusal on the message you just sent. A long conversation is summarised in more than one call, and you will not notice. The summariser reads a bounded amount of the oldest history at a time, so a conversation far past the threshold is compacted in a few passes on the same turn rather than in one enormous call. This is invisible: the conversation carries one summary, the newest, and it covers everything the earlier ones did. It is bounded that way because one call large enough to swallow a whole conversation is a call no provider’s per-minute budget will serve, which used to leave a thread at once too large to send and too large to compact. Those passes stop when the conversation is small enough to actually be asked, not merely small enough for the window. Both measures from the top of this page decide when to stop, exactly as they decide when to start: summarising until only the window looked satisfied is what used to leave a conversation looking finished while the model still refused it. A conversation that crosses the threshold in the ordinary way is summarised once and carries on. The compaction_ prefix belongs to this automatic path alone. The endpoint below, which you call on purpose, never uses it: see its own refusals.

Compacting on purpose

A person can ask for a summary before the wall forces one: POST /chat/v1/threads//compact There is no body. Asking for a compaction is the whole request, and there is nothing to configure: this is a button, not a form. Nobody reads a summary, and nobody steers one. There is no way to say what a summary should keep, and the summary itself is never served: compaction is meant to be invisible, and what the model is shown from here is the platform’s business rather than a document to review. It answers with the marker, which is the same pair the thread read carries, so a client draws the line the same way whichever call it made:
uuid identifies the compaction itself. It is a handle, not content: it is what you quote when you ask us about a particular compaction, and there is nothing behind it you can read. This endpoint makes one bounded pass, so on a very long conversation it can answer 200 with the conversation still above the threshold. The summariser reads a bounded amount of the oldest history per call, exactly as the automatic path does, and unlike the automatic path this one does not repeat: a person is waiting on the response. If the conversation is still long enough to matter, press it again. Nothing is lost by doing so, and each press folds the previous summary into the new one. Every refusal this endpoint answers on its own carries its own code, in its own words, and none of them carry the compaction_ prefix above: that prefix belongs to the automatic path, and this one answers synchronously, in the response body, rather than as a run’s terminal state. Any other refusal the pipeline itself makes (an exhausted balance, a provider fault) passes through exactly as it would for an ordinary turn, with its own status code, because this endpoint has no better answer for those than the pipeline’s own.

Attached documents

A document attached to a conversation is summarised with it. Its extracted text is part of what the model reads on every turn, so it is part of what pushes a conversation toward the wall, and it goes into the summary under a line naming the file. Where the firewall has stored a masked form of that text, the masked form is what the summariser reads, exactly as on an ordinary turn. A file nothing could read into text (a picture, or a format the extractor does not know) leaves a line naming the file and saying no text was extracted, so a summary can say a document was present even when it cannot say what it held. A file whose retention window has passed leaves no line at all: its bytes and its text are deleted, nothing is sent for it on an ordinary turn either, and telling the summariser it could not be read would be untrue in a way the summary would then repeat for the life of the conversation. A file still waiting for somebody to answer for it is left out altogether, the same way it is left out of the prompt itself until that answer arrives. Like a tool result, a document’s text is truncated into the summary at 2,000 characters with a marker saying how many were dropped: what survives is that the file was there and roughly what it was about.

Two honest limits

A tool call’s arguments are never sent to the summariser. They are stored unmasked, on every message, in every workspace, firewall on or off, because pseudonymizing them happens only for the live model call and is never written back to the row. Sending that plaintext to the summariser under a label that says “already safe” would be worse than not sending it: nothing downstream would catch it. So only the tool’s name survives into the summary; a tool’s result is an ordinary message and is summarised like any other, so a summary can still say what a tool found. It cannot say what it was asked. On a route that is not redacted, the summariser reads the same plaintext the conversation’s own model reads. Compaction sends the conversation’s own model exactly what that model would otherwise be sent, on the same routing decision your workspace already made: an external route gets the masked form, an EU-native route gets plaintext unless your workspace has turned on redacting its own EU-native route. The summariser is not held to a stricter standard than the conversation it summarises. It sees exactly what that model sees, no more and no less.