feat(runtime): give each project its own conversations - #52
Merged
Conversation
Woopcode kept one conversation for the whole machine. `getConversationPath` returned a single global `conversation.json`, and `getExecutionLogPath` a single `execution-log.json` beside it. Three things followed, all of them live: `/new` destroyed history — it called `saveConversation([])`, and the guide said outright that there was no undo. History leaked between repositories, which the guide also documented, as a warning rather than a bug: a session started in one project restored into a turn taken in another, execution log included, which the model reads as a description of what has already been done *here*. And there was no way back to anything: no ids, no listing, no resume, no branching. A session is now one record under `sessions/<project-slug>/<id>.json`, keyed by the repository root. The slug carries a hash of the full path because slugifying alone maps `/a/b` and `/a-b` to the same name, which would silently merge two unrelated projects. `index.json` beside them is a derived cache: delete it, or let it fall behind the files, and it is rebuilt by scanning. A bare launch continues the newest session in the project, which is what the documentation has always promised, now correctly scoped. `-p` starts its own instead of inheriting whatever was open interactively — a surprise a scripted caller could not see or opt out of. Nothing is written until a turn has run, so starting Woopcode and quitting leaves nothing behind. Titles come from the first prompt rather than a model. A generated title costs a provider request per session on whichever of the three is configured, and adds a failure path at the moment a session is created; `/rename` is there for anyone who wants better. History from before sessions existed is imported once into a `legacy` bucket rather than attributed to whichever project is opened first — it was shared by every repository, so no project can honestly claim it. Take a turn in it and it is adopted by the project you are working in; open it to read and it stays put. Two windows on one session used to lose a turn, each writing the whole record so the later save discarded the earlier one. The single conversation file behaved the same way, so this is inherited rather than new, but it is silent loss of someone's work: it is now detected and answered by branching, leaving the other window untouched.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Woopcode kept one conversation for the whole machine.
getConversationPathreturned a single global
conversation.json, andgetExecutionLogPatha singleexecution-log.jsonbeside it. Three things followed, all of them live onmain:/newdestroyed history. It calledsaveConversation([]), anddocs/guides/sessions-and-history.mdsaid outright that there was no undo.warning rather than a bug: "there is one history file, not one per repository
… use
/newwhen you switch projects, or the agent begins with context fromsomewhere else entirely." It applied to the execution log too, which reaches
the model through
contextForTurnas a description of what has already beendone here.
--continue, no--resume, no branching.What
A session is one record under
sessions/<project-slug>/<id>.json, keyed by therepository root.
index.jsonbeside them is a derived cache — delete it, or letit fall behind the files, and it is rebuilt by scanning.
/resume(picker or by ref),/sessions,/rename,/branch;/newkeeps the old session and prints the id to get back to it-c/--continue,--resume [ref],--new,--fork-session,-n/--name,--no-session-persistencewoopcode sessions list | show | pruneretentionDaysinproviders.json, default 30,0keeps foreverDecisions worth arguing with:
/a/band/a-bto the same directory, silently merging two unrelated projects.-pdoes not. Continuing is what the docs havealways promised, now correctly scoped. A headless run inheriting whatever was
open interactively is a surprise a scripted caller cannot see or opt out of.
provider request per session on whichever of the three is configured, and adds
a failure path at the moment a session is created.
/renamecovers the rest.legacybucket, not in whichever projectis opened first — it was shared by every repository, so no project can honestly
claim it. Take a turn in it and it is adopted by the project you are working
in; open it to read and it stays put.
leaves no empty session for the picker to offer.
Two windows on one session used to lose a turn, each writing the whole record so
the later save discarded the earlier.
saveConversationonmainbehaved thesame way, so this is inherited rather than introduced — but it is silent loss of
someone's work, so it is now detected and answered by branching, leaving the
other window untouched.
Verified
A first pass went green on every gate and sweep, and reading the diff afterwards
found seven defects — one of which made
--fork-sessionwrite into the sessionit exists to protect. All are fixed, and each regression test was checked by
reverting its fix and confirming it goes red:
forkSessionscoped to the current project/branchfailed on legacy and cross-project sessionsfork() ?? originalfallbackpruneIfDuewrote an index to record itself--resume--continue/resumereturned""The replay comparison matters because moving the execution log into the session
changes what the prompt carries; it does not.
Also exercised by hand: migration importing and retiring its source, legacy
history invisible to a project but visible under
--all,sessions show/prune,lazy creation leaving no directory behind, exit codes, and the benchmark
harness's
woopcode -pinvocation shape (fresh session per trial under its ownXDG_CONFIG_HOME, unchanged events and exit codes).Not verified: no live-provider turn — everything through the agent loop runs
against
MockProviderClient, and the one real request made was an auth failurethat proved the session path only. The session picker has a render test and store
unit tests but its keyboard flow has never been driven by hand, so whether
ink-text-inputswallows CtrlA in the search field isstill open; Tab is the fallback if it does.