Context
The transcript and the model's context are different things.
The journal is the whole conversation and is never edited. The context is what a responder receives as messages for one turn: a projection of the transcript, built fresh each turn by the chat's context, from the current session only. June adds no hidden memory across sessions and no retrieval.
journal (every entry, forever) ──▶ turns() ──context.build()──▶ messages for this turn ──▶ responder
Say a session has 50 completed turns. With summaryContext({ keepRecent: 8, compactAfter: 20 }), the next turn's responder gets a summary of the older turns and the most recent ones verbatim. The journal, turns() and messages() still hold all 50. Changing the context strategy changes what later turns see, never what's stored.
fullContext()
The default: every completed turn, in order, as a user and an assistant message. Failed, canceled and interrupted turns are left out, so a question that was never answered doesn't reach the model as if it had been. A turn's accepted steering appears as an extra user message between its input and its reply.
import { openai } from "june/sdk/openai";
import { createChat, fullContext } from "june/chat";
const chat = createChat({
model: openai("gpt-5-mini"),
context: fullContext(),
});
summaryContext()
Long conversations eventually outgrow a model's context window. summaryContext() replaces older turns with a summary another model writes, and sends the most recent turns as they are:
import { openai } from "june/sdk/openai";
import { createChat, fileStore, summaryContext } from "june/chat";
const chat = createChat({
model: openai("gpt-5"),
store: fileStore(".june/chat"),
context: summaryContext({
model: openai("gpt-5-mini"),
keepRecent: 8,
compactAfter: 20,
}),
});
- Up to
compactAftercompleted turns, it's the same asfullContext(). - Past that, the context is one user message starting
Summary of the earlier conversation:, then the recent completed turns verbatim. The responder adds the new input after it, as with any context. The summary is clearly marked, so it never passes for something the user said. - The boundary moves in steps of
compactAfter - keepRecentturns, so one summary serves several turns before it's rewritten. The same turns always give the same boundary, including after a session is reopened. - It counts turns, not tokens. June has no tokenizer and makes no promise about how many tokens a context uses; pick counts that fit your models with room to spare.
- Summaries are derived data. They're cached in memory, one per session, for the life of the
summaryContext(), and never journaled, soturns(),messages()and the journal file keep every message, and a session reopened in a new process writes its summary again on its first long turn. - Writing a summary is part of answering the turn. If the summarizer fails, the turn fails; canceling the turn aborts it. Either way the journal keeps every earlier turn.
The summarizer receives every turn it summarizes. If that's a different model or provider from the one answering, it's one more place your conversations go. Pass instructions to say how to summarize. The default instructions and the wording of the summary marker may change in a minor version; see Compatibility.
src/examples/chat/context.ts runs a long chat that's summarized for the model while its transcript keeps every message.
What context isn't
Context is built only from the current session's earlier turns. There's no memory across sessions, no retrieval over other conversations or documents, and nothing added behind your back. Compacting the context deletes nothing from the transcript. A model sees what the context returns, plus whatever its responder adds itself: a model chat's instructions, or what a workflow's steps put in their prompts. If you want retrieval or long-term memory, build it into a custom context or your workflow, where you can see it.
Custom context
A context is a projection: from the session's history to the messages for this turn. Anything with a build() method is one, and it's also where application retrieval goes, explicitly, if you want it. build() gets the session id, a copy of every earlier turn (all statuses, with feedback), the new input and the turn's signal, and returns AI SDK messages:
import { createChat, type ContextBuilder, type Responder } from "june/chat";
declare const responder: Responder;
/** The last few completed turns, plus the reply a person preferred for each. */
const recentWithEdits: ContextBuilder = {
build({ turns }) {
return turns
.filter((turn) => turn.status === "completed")
.slice(-4)
.flatMap((turn) => {
const edit = turn.feedback.findLast((f) => f.preferred)?.preferred;
return [
{ role: "user" as const, content: turn.input },
{ role: "assistant" as const, content: edit ?? turn.output! },
];
});
},
};
const chat = createChat({ responder, context: recentWithEdits });
Whatever it returns is only what the responder sees this turn; changing the turns it's given changes nothing stored. A build() that throws fails the turn.