Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ The conversation, provider configuration, and local state are stored in:

### Session history

A session is one saved conversation, belonging to the project it happened in. It is written after every turn using an atomic write, so an interrupted session does not leave a half-written transcript behind. Restarting Woopcode in the same repository resumes the newest one; starting it somewhere else does not, because sessions live under `sessions/<project>/` rather than in one file shared by every repository.
A session is one saved conversation, belonging to the project it happened in. It is written after every turn using an atomic write, so an interrupted session does not leave a half-written transcript behind. Restarting Woopcode opens a fresh session rather than reopening the last one, because a conversation restored without being asked for is state nothing on screen accounts for. Sessions live under `sessions/<project>/` rather than in one file shared by every repository, so a resume only ever offers you that repository's own.

`/new` starts a fresh session and keeps the old one — `/resume` goes back to it, `/rename` gives it a name, `/branch` copies it to try a second approach, and `woopcode --continue` / `--resume` do the same from the command line. Sessions are deleted 30 days after their last turn; `retentionDays` changes that and `0` keeps them forever.

Expand Down
25 changes: 13 additions & 12 deletions commands/agent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ export interface RunAgentOptions {
/**
* Turns the session flags into what `AgentController.initialize` takes.
*
* The two entry points differ in one default and it matters: the TUI continues
* where you left off, because that is what Woopcode has always done and what
* the documentation promises, while `-p` starts clean. A headless run used to
* inherit whatever the interactive session had been doing, which is a surprise
* for a scripted caller and impossible to opt out of.
* Both entry points start clean, and resuming is something you ask for. The TUI
* used to continue the newest session in the project on a bare launch, which is
* invisible state: nothing on screen says a conversation was restored, and the
* home screen — which renders only on an empty timeline — silently stopped
* appearing from the second launch onwards. `-p` already worked this way,
* because inheriting whatever the interactive session had been doing is a
* surprise a scripted caller cannot see coming.
*/
export function sessionOptionsFrom(
options: RunAgentOptions,
Expand All @@ -66,10 +68,7 @@ export function sessionOptionsFrom(

return {
...(resumeRef ? { sessionRef: resumeRef } : {}),
continueLatest:
!options.new &&
!resumeRef &&
(options.continue === true || mode === "interactive"),
continueLatest: !options.new && !resumeRef && options.continue === true,
fork: options.forkSession === true,
...(options.name ? { name: options.name } : {}),
persist: options.sessionPersistence !== false,
Expand All @@ -85,7 +84,7 @@ export function addSessionOptions(command: Command): Command {
return command
.option("-c, --continue", "resume the newest session in this project")
.option("--resume [session]", "resume a session by name or id")
.option("--new", "start a fresh session instead of continuing")
.option("--new", "start a fresh session (the interactive default)")
.option("--fork-session", "with --continue or --resume, branch instead of writing into it")
.option("-n, --name <name>", "name a new session so it can be resumed by name")
.option("--no-session-persistence", "with --prompt, do not save the session");
Expand Down Expand Up @@ -409,8 +408,10 @@ async function runInteractive(
store.hydrateTimeline(resumed.messages);
}

// A bare `--resume` asks to choose. The newest session is loaded above so
// there is something behind the dialog and something to fall back to on Esc.
// A bare `--resume` asks to choose. Nothing is loaded behind it — a launch no
// longer continues anything on its own — so the dialog opens over the home
// screen and Esc leaves you in the fresh session rather than in whichever one
// happened to be newest.
if (session.openPicker) store.openSessionPicker();

const homeScreen = await buildHomeScreen(provider);
Expand Down
15 changes: 7 additions & 8 deletions docs/getting-started/first-session.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,13 @@ A few commands are worth knowing on day one:
| `/new` | Clear the conversation and start fresh |
| `/workspace` | Show the repository Woopcode thinks it is in |

Your conversation is saved after every turn, so quitting and restarting resumes
where you left off. `/new` is how you deliberately drop that history.

:::warning
There is one history file, not one per repository. Starting Woopcode in a
different project resumes the same conversation, so run `/new` when you switch
— otherwise the agent begins with context from somewhere else entirely.
:::
Your conversation is saved after every turn, so nothing is lost when you quit.
Starting Woopcode again opens a fresh session rather than reopening it —
`woopcode --continue` goes back to the newest one, and `/resume` picks from a
list. `/new` starts a fresh one without leaving the session you are in.

Sessions belong to the project they happened in, so a repository only ever shows
you its own. See [Sessions & history](/docs/guides/sessions-and-history).

## When it does not work

Expand Down
15 changes: 8 additions & 7 deletions docs/guides/sessions-and-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ since: 0.6.0
# Sessions & history

A session is one saved conversation, belonging to the project it happened in.
It is written after every turn, so quitting and coming back picks up where you
left off — in that repository, and not in any other.
It is written after every turn, so nothing is lost when you quit — but coming
back to it is something you ask for.

## Resuming

Starting Woopcode in a project reopens the newest session there. The rest:
Starting Woopcode opens a fresh session. Going back to an old one:

| Command | What it does |
| --- | --- |
| `woopcode --continue` | Reopen the newest session in this project |
| `woopcode --resume <name-or-id>` | Reopen a particular one |
| `woopcode --new` | Start fresh instead of continuing |
| `woopcode --resume` | Pick from a list before starting |
| `woopcode --new` | Start fresh — what a bare launch already does |
| `/resume` | Pick from a list, without leaving the session you are in |
| `/resume <name-or-id>` | Switch straight to one |
| `/sessions` | List what is saved in this project |
Expand Down Expand Up @@ -121,9 +122,9 @@ Sessions are deleted 30 days after their last turn. Change it in

## Two windows on one conversation

Open Woopcode twice in the same repository and both continue the newest session.
Each turn writes the whole record, so the second window would overwrite the
first's work.
Two windows land on the same conversation when both resume it — `--continue` in
each, or `/resume` onto one the other already has open. Each turn writes the
whole record, so the second window would overwrite the first's work.

It does not: a session that changed underneath a window is detected, and that
window's turn is kept as a branch with its own id, leaving the other window's
Expand Down
4 changes: 2 additions & 2 deletions docs/introduction/how-a-turn-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ approval dialog. See [Keyboard](/docs/reference/keyboard).

## Afterwards

The conversation is saved, globally rather than per repository — restarting
Woopcode anywhere resumes from it. See
The conversation is saved, to the project it happened in. Restarting Woopcode
opens a fresh session; `--continue` or `/resume` goes back to this one. See
[Sessions & history](/docs/guides/sessions-and-history).

## Next
Expand Down
11 changes: 6 additions & 5 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ directory.
| `-m, --model <model>` | Model id for this run only; the saved selection is left alone |
| `-c, --continue` | Reopen the newest session in this project |
| `--resume <session>` | Reopen a session by name, id or id prefix |
| `--new` | Start a fresh session instead of continuing |
| `--new` | Start a fresh session — what a bare launch already does |
| `--fork-session` | With `--continue` or `--resume`, branch instead of writing into it |
| `-n, --name <name>` | Name a new session so it can be resumed by name |
| `--no-session-persistence` | With `--prompt`, do not save the session |
Expand Down Expand Up @@ -57,14 +57,15 @@ woopcode

### Sessions

A bare launch reopens the newest session in that project. The flags pick a
different one:
A bare launch starts a fresh session, keeping whatever was saved before. The
flags go back to one:

```bash
woopcode --continue # explicit about the default
woopcode --continue # the newest session in this project
woopcode --resume auth-refactor # by name
woopcode --resume 3f9c1a2b # by id prefix
woopcode --new # start fresh, keeping the old one
woopcode --resume # pick from a list
woopcode --new # explicit about the default
woopcode --continue --fork-session # branch rather than continue in place
woopcode -n auth-refactor # name the session as it starts
```
Expand Down
23 changes: 15 additions & 8 deletions packages/tests/config/sessionFlags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { sessionOptionsFrom } from "../../../commands/agent";
/**
* The mapping from command-line flags to what the controller resolves.
*
* Pure and worth testing on its own: the two entry points differ in one default
* and getting it backwards is invisible until someone loses a conversation.
* Pure and worth testing on its own: getting a default backwards here is
* invisible until someone loses a conversation, or until a restored one starts
* hiding the home screen.
*/
describe("sessionOptionsFrom", () => {
test("a bare interactive launch continues where you left off", () => {
// The documented promise, now scoped to the project you are in.
expect(sessionOptionsFrom({}, "interactive").continueLatest).toBe(true);
test("a bare interactive launch starts a fresh session", () => {
// Continuing on a bare launch is invisible state — nothing says a
// conversation was restored, and the home screen quietly stops appearing.
expect(sessionOptionsFrom({}, "interactive").continueLatest).toBe(false);
});

test("a bare headless run starts its own session", () => {
Expand All @@ -19,12 +21,17 @@ describe("sessionOptionsFrom", () => {
expect(sessionOptionsFrom({}, "headless").continueLatest).toBe(false);
});

test("--continue asks for it explicitly, headlessly too", () => {
test("--continue asks for it explicitly, in both modes", () => {
expect(sessionOptionsFrom({ continue: true }, "interactive").continueLatest).toBe(true);
expect(sessionOptionsFrom({ continue: true }, "headless").continueLatest).toBe(true);
});

test("--new overrides the interactive default", () => {
expect(sessionOptionsFrom({ new: true }, "interactive").continueLatest).toBe(false);
test("--new beats --continue rather than being ignored beside it", () => {
// --new is now the default rather than an override, but a script that
// passes both is asking for a fresh session and has to get one.
expect(
sessionOptionsFrom({ new: true, continue: true }, "headless").continueLatest,
).toBe(false);
});

test("--resume <ref> resolves a reference instead of continuing", () => {
Expand Down