Chat
A workflow runs once and finishes. june/chat keeps an interaction alive across turns: a session records the conversation, can be reopened later, and stores people's feedback beside the replies it judges.
Quickstart
Give createChat any AI SDK model:
import { openai } from "june/sdk/openai";
import { createChat } from "june/chat";
const chat = createChat({
model: openai("gpt-5-mini"),
instructions: "Be concise.",
});
const session = await chat.createSession();
const turn = session.send("What is June?");
for await (const event of turn) {
if (event.type === "text-delta") process.stdout.write(event.text);
}
const { text, metadata } = await turn.result;
console.log(
metadata?.model,
metadata?.usage?.totalTokens,
metadata?.finishReason,
);
await session.send("Say that in five words.").result;
A chat is configuration and does no IO when you create it. createSession() starts a conversation, send() starts a turn, and the second turn sees the first. src/examples/chat/model.ts runs as-is with an OPENAI_API_KEY.
A persistent chat
Give the chat a fileStore() and its sessions survive restarts. A file that default exports a chat also runs in the terminal:
import { openai } from "june/sdk/openai";
import { createChat, fileStore } from "june/chat";
export default createChat({
model: openai("gpt-5-mini"),
instructions: "You are helping build June.",
store: fileStore(".june/chat"),
});
june chat chat.ts # an interactive chat in your terminal
june ask chat.ts "Explain this package" # one reply on stdout, for scripts
june ask chat.ts --session <id> "And its tests?" # continue a saved session
The CLI reference documents both commands. From your own code, reopen the same session with chat.openSession(id): src/examples/chat/persistent.ts continues a session in a new process. Add .june/ to .gitignore: journals hold every input and reply in plain text. Sessions and stores covers the choice of store.
How it fits with the rest of June
A turn is answered by a responder, and there are two built in:
- Model-backed.
createChat({ model }), as above, answers each turn with one AI SDKstreamTextcall. It only talks: no tools. - Workflow-backed.
agentResponder()answers each turn by running a June workflow withstart(). Steps, tools, approvals,<Catch>, timeouts and the trace are the runtime's own, unchanged. Chat adds the conversation around the run.
Or write your own; Responders helps you choose.
Chat doesn't need anything else, but its journal is also where improving a chat starts. Ratings and preferred edits are stored next to the replies they judge, and june/train can read sessions into datasets and eval cases. That's evidence, not a change: nothing about what answers your chat changes until a person evaluates a candidate and promotes it. See Learning from chat. june/chat never depends on june/train.
The Copy Agent is a complete application built this way: each turn runs a Copy workflow through agentResponder(), people correct replies with preferred edits, and those edits become the evidence a candidate voice is evaluated on.
Chat lives only under june/chat; the root june export doesn't re-export it.
Architecture
Chat reusable configuration: how turns are answered, where sessions are stored
└ Session one conversation, with a stable id and a durable journal
└ Turn one user input, its execution, and the assistant's reply
└ Responder the thing that answers the turn
- Chat: configuration. A responder (or a model, as shorthand), a store, and a context strategy.
- Session: one conversation. Its journal, an append-only log of plain JSON entries, is the source of truth;
turns()andmessages()are rebuilt from it. - Turn: one input, answered once. Turns run one at a time, stream live events, and end as completed, failed, canceled or interrupted. A turn is cancelable, and steerable when its responder allows.
- Responder: what answers. A model, a June workflow, or anything implementing
respond(). A workflow turn is one June run; the session outlives every run. - ChatStore: where journals live. Memory, local files, or your database.
june chat / june ask / your app
│ send(input) live ChatEvents
▼ (text-delta, run-update,
┌──────────────────── Session ────────────────────┐ approval-request, …)
│ turns() / messages() ◀── projection ──┐ │ ───────────────────────▶
│ │ │
│ Turn: accepted ─▶ context ─▶ Responder ─▶ outcome
│ │ │ │ │
└──────────┼───────────┼───────────┼─────────────┘
▼ │ ▼
ChatStore journal │ modelResponder ── AI SDK streamText
(append-only, │ agentResponder ── June start() ── <Agent>/<Step>/tools
canonical) │ your responder
▼
fullContext() / summaryContext() / yours
Live events are for showing progress and are never stored; journal entries are the few facts worth keeping. Later, and only if you choose, june/train reads the journal:
journal ──read by──▶ june/train: datasets, preference pairs, eval cases
What Chat isn't
- Not a UI framework. There's no browser UI.
june chatis a terminal loop; a web or mobile UI is yours to build on the same sessions and events. - Not authentication. June doesn't know who is talking or who may read a session. Your application decides that before it opens one.
- Not a hosted service or a database. Sessions live in the store you choose: memory, local files, or your own database.
- Not exactly-once durable execution. With a persistent store such as
fileStore(), an accepted turn's input survives a crash, but its work doesn't resume, and June never retries a turn on its own. A retried turn runs again from the start, tools included. - Not a provider registry. A chat uses whatever model its code builds. Turns are text in and text out, with no attachments.
- Not hidden memory or retrieval. The model sees what the context strategy builds from earlier turns in the same session, and nothing else. There's no memory across sessions and no retrieval.
- Not a second workflow runtime. A workflow-backed turn is one ordinary June run.
Guides
- Sessions and stores: create, reopen and persist a conversation;
memoryStore(),fileStore(), your own store; durability - Turns and streaming: a turn from input to settled reply; cancel, steer, failures and retry
- Responders: model chats, workflow chats with
agentResponder(), your own, provenance - Context: the transcript versus what a responder sees;
fullContext()andsummaryContext() - Feedback and preferred edits: ratings and edits, recorded as evidence
- Learning from chat: from sessions to datasets, eval cases and a promoted candidate
- Privacy and security: what leaves your machine, and what's stored
Runnable examples are in src/examples/chat, and the compatibility page lists which chat contracts are stable.