SDK

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:

Shell
~/.harnext/agent/sessions/<cwd-hash>/<sessionId>.jsonl
  • Per-directory scoping. <cwd-hash> is getProjectHash(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 AgentMessage records are kept, not a flattened summary, so the API usagesurvives — 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.

Shell
harnext --resume

On 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

Shell
# 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.

TypeScript
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.

TypeScript
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.

TypeScript
const { session } = await createAgentSession({
  initialMessages: history,
  sessionId: 'my-id',
});
createAgentSession resume options
  • resumeSessionId?: string — load the stored transcript by id and continue under it.
  • initialMessages?: AgentMessage[] — seed history directly. Takes precedence over resumeSessionId.
  • sessionId?: string — stable id the continued session persists under.
Result: { 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.

TypeScript
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 cap

SessionSummary carries { sessionId, firstUserMessage, messageCount, model, provider, createdAt, updatedAt, filePath } — everything a picker row needs.

System prompt vs. history
The system message is configured separately from the conversation. Set it via 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.

JSON
{"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:

TypeScript
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');
Source
Resumable sessions shipped via QualityUnit/harnext#44 (feature request) and #46 (implementation). See the announcement post for a walkthrough.