Resuming sessions
Continue a previous conversation with its full context — interactively with the CLI's --resume flag, or programmatically through @harnext/core.
Every harnext run is recorded as a durable transcript. Resuming seeds a new session with that prior history so the agent continues exactly where it left off — the plan, the files it read, the decisions you made together. This page covers the on-disk model, the CLI --resume flow, and the @harnext/core API for resuming and managing sessions.
Concepts
A session is the full message history of one run: user turns, assistant turns, and tool results, each kept as a complete AgentMessage. Transcripts are stored as append-only JSONL at:
~/.harnext/agent/sessions/<cwd-hash>/<sessionId>.jsonl- Per-directory scoping.
<cwd-hash>isgetProjectHash(absoluteCwd)— the first 12 hex characters of a SHA-256 of the absolute working directory. Sessions belong to the project they ran in, so the picker only shows this directory's runs. - Append-only. Each turn appends one line; the file is only rewritten when the agent replaces its history (compaction).
- Lossless. Full
AgentMessagerecords are kept, not a flattened summary, so the APIusagesurvives — that's what the resume flow reads to measure context size. - Retention. harnext keeps the
DEFAULT_MAX_SESSIONS_PER_CWD(100) most recent sessions per directory, pruning oldest-first as new transcripts are created.
CLI
Pick from a list
harnext --resume (alias -r) opens an interactive picker of the current directory's sessions, newest first. Each row shows the first user message, a relative timestamp, the message count, and the model. Press enter to resume with full context.
harnext --resumeOn resume, the prior transcript is replayed into the terminal — user echoes, tool badges and output, rendered markdown — so you see the conversation rather than a blank screen.
Resume a specific session
# Resume a known session id directly (skips the picker)
harnext --resume 3f9c1b2a
# Scripted resume in print mode
harnext -p --resume 3f9c1b2a "now add tests for it"Summarize on threshold
When a resumed conversation is near the model's context window (contextWindow − reserveTokens), the CLI offers to compact it via the same compactNow pipeline a live session uses, and shows a before/after token estimate so you can see what was reclaimed.
SDK quickstart
createAgentSession accepts three resume-related options, in increasing order of control.
Resume by id
Load a transcript from harnext's local per-cwd store and continue under the same id.
import { createAgentSession } from '@harnext/core';
const { session, sessionId, resumed } = await createAgentSession({
cwd: process.cwd(),
resumeSessionId: '3f9c1b2a-…',
});
// resumed === true when prior history was seeded
await session.prompt('continue where we left off');Resume by value
Supply the transcript yourself — useful when history lives in your own store. initialMessages takes precedence over resumeSessionId.
import { createAgentSession, type AgentMessage } from '@harnext/core';
const history: AgentMessage[] = [
{ role: 'user', content: 'build a CLI', timestamp: 1 },
{ role: 'assistant', content: [{ type: 'text', text: 'On it.' }], timestamp: 2 },
{
role: 'toolResult',
toolCallId: 'c1',
toolName: 'bash',
content: [{ type: 'text', text: '…' }],
isError: false,
timestamp: 3,
},
];
const { session } = await createAgentSession({ initialMessages: history });Control the persisted id
Pass sessionId alongside initialMessages to dictate the id the continued session persists under.
const { session } = await createAgentSession({
initialMessages: history,
sessionId: 'my-id',
});resumeSessionId?: string— load the stored transcript by id and continue under it.initialMessages?: AgentMessage[]— seed history directly. Takes precedence overresumeSessionId.sessionId?: string— stable id the continued session persists under.
{ session, sessionId, resumed, diagnostics } — sessionId is the resolved id (persist it to resume later) and resumed is true when prior history was seeded.Managing sessions
@harnext/core exports the helpers you need to build a custom picker or do housekeeping.
import {
listSessions,
loadSession,
deleteSession,
pruneSessions,
getSessionFilePath,
getCwdSessionsDir,
} from '@harnext/core';
// Newest-first summaries for the current directory
const sessions = listSessions(process.cwd());
for (const s of sessions) {
console.log(s.firstUserMessage, s.messageCount, s.model, s.updatedAt);
}
// Load a full transcript (id-only lookup also works)
const stored = loadSession(sessions[0].sessionId);
// Housekeeping
deleteSession(process.cwd(), sessions[0].sessionId);
pruneSessions(process.cwd()); // enforce the per-cwd capSessionSummary carries { sessionId, firstUserMessage, messageCount, model, provider, createdAt, updatedAt, filePath } — everything a picker row needs.
systemPrompt (full override) or appendSystemPrompt — do not add it as a turn in initialMessages.Storage format reference
A transcript is JSONL: the first line is a session-meta record, and every subsequent line is one AgentMessage. This is enough to read — or write — a transcript by hand.
{"type":"session-meta","version":1,"sessionId":"3f9c1b2a","cwd":"/home/me/projects/api","model":"claude-opus-4-8","provider":"anthropic","createdAt":1718370000000}
{"role":"user","content":"Add rate limiting to the search endpoint","timestamp":1718370001000}
{"role":"assistant","content":[{"type":"text","text":"On it."}],"model":"claude-opus-4-8","usage":{"input_tokens":1820,"output_tokens":42},"timestamp":1718370002000}
{"role":"toolResult","toolCallId":"c1","toolName":"bash","content":[{"type":"text","text":"…"}],"isError":false,"timestamp":1718370003000}Write your own recorder with createSessionWriter(opts), whose record(messages) appends at each turn boundary. The package also exports the SESSION_FILE_VERSION and DEFAULT_MAX_SESSIONS_PER_CWD constants and the StoredSession, StoredSessionMeta, SessionSummary, SessionWriter, and AgentMessage types (re-exported so callers don't depend on pi-agent-core directly).
Turning a read-only restore into a live session
A desktop or web client that has loaded a transcript for display can make it editable again by seeding a fresh session with the same messages and id:
import { createAgentSession, loadSession } from '@harnext/core';
const stored = loadSession(sessionId, cwd);
const { session } = await createAgentSession({
initialMessages: stored.messages,
sessionId: stored.sessionId, // continue persisting under the same id
});
// The previously read-only conversation is now live
await session.prompt('keep going');