Goal
Create a first-run DEMO experience that lets a new Human understand and try PlotPickle before creating or unlocking a private local profile.
This should reduce onboarding friction without weakening the existing profile, Guest, storage, canon or agent-authority boundaries.
Why
PlotPickle is easier to understand by experiencing it than by reading a feature list. A new user should be able to launch the app, choose DEMO, interact with a small synthetic story world, see how decisions propagate, and then decide whether to create a real local profile.
The experience should borrow the useful idea behind HumanLayer's show-me skill: explain the current thing visually with the smallest useful view. For PlotPickle, this becomes a bounded Sage capability rather than a dependency on HumanLayer.
Refs #1675 and the existing profile/auth work in #1143.
First-run shape
On a fresh installation, present two clear choices:
-
DEMO � See PlotPickle work
- no account, password, GitHub, BUZZ identity, provider key or cloud setup required;
- opens a synthetic, disposable PlotPickle world;
- safe to reset at any time.
-
ENTER PLOTPICKLE � Create or unlock your private profile
- uses the existing local-profile/authentication architecture unchanged.
For returning users with an existing local profile, normal startup may continue to favor the profile chooser while keeping DEMO available as a secondary action.
Architectural boundary
DEMO must be a separate bounded runtime. It must NOT silently expand the current Guest surface.
Existing Guest behavior remains unchanged: ephemeral notes only, no mounting of the private PlotPickle application, and deletion on exit.
DEMO may mount a curated application experience only against synthetic/demo-owned data. It must have no read/write access to:
- private Human profiles;
- private projects or libraries;
- Human Wyrmwood state;
- BUZZ private identity/context;
- provider credentials;
- GitHub/Google connections;
- real PPF canon;
- local filesystem paths outside the demo-owned sandbox.
Demo content
Start with one small prepared STORY scenario, ideally 5–10 minutes.
The demo should visibly demonstrate a few uniquely PlotPickle behaviors rather than expose every workspace:
- a character makes or receives a consequential decision;
- STORY deterministically recalculates the result;
- character/private knowledge remains hidden from the wrong participant;
- a Story Piece or structured consequence is visible;
- PPF/canon authority is explained without allowing DEMO to mutate real canon;
- Wyrmwood presents a playable/story consequence where useful;
- Sage explains what changed and why.
Sage "Show Me" capability
Add a narrow explanatory mode for Sage that selects the smallest useful visual representation for the current concept.
Possible views:
- before/after state change;
- character knowledge partition;
- decision → consequence flow;
- scene/character/object relationship map;
- STORY vs PPF vs Wyrmwood vs BUZZ authority diagram;
- agent permission/authority boundary.
This is an explanatory projection only. It must not gain new write authority or bypass existing trust/canon checks.
Conversion into the real product
At natural points, DEMO may offer Make This Mine / Start My Story.
That action must:
- leave the disposable DEMO authority boundary;
- create or unlock a real local Human profile through the existing profile flow;
- create a fresh real project;
- copy only explicitly approved demo-derived starter content;
- never copy demo credentials, hidden system state, runtime authority or synthetic identity artifacts.
A user must also be able to exit DEMO with no profile creation and no retained private state.
Suggested phases
Phase 0 � Contract and boundary
- define DEMO runtime authority and data ownership;
- explicitly preserve Guest and auth behavior;
- define reset/delete semantics;
- define allowed handoff payload into a new real profile.
Phase 1 � Deterministic demo world
- bundle one small synthetic STORY scenario;
- add resettable disposable state;
- prove no private profile/canon/provider access;
- support replay from a known seed/state.
Phase 2 � First-run UI
- add DEMO and ENTER PLOTPICKLE choices;
- preserve returning-user profile behavior;
- provide clear exit/reset controls;
- avoid setup/provider prompts inside DEMO.
Phase 3 � Sage Show Me
- add concise visual explanation projections;
- use existing STORY/PPF/agent data contracts rather than inventing a second graph or authority model;
- keep explanatory output read-only.
Phase 4 � Make This Mine
- explicit transition into profile creation/unlock;
- copy only approved starter content into a fresh project;
- verify DEMO state cannot become privileged state.
Phase 5 � UAT and packaging
- fresh-install UAT from installer → DEMO → interaction → reset → exit;
- DEMO → Make This Mine → real profile/project UAT;
- verify private profile isolation with an existing profile present;
- update README/onboarding docs;
- compile and smoke-test
PlotPickleSetup.exe before release readiness.
Acceptance criteria
- A fresh user can experience useful PlotPickle behavior without creating a profile.
- DEMO works without Internet, BUZZ, GitHub, Google or external AI credentials where deterministic local demo behavior is sufficient.
- DEMO cannot read or write any existing Human-private state.
- Guest remains its existing restricted ephemeral-notes boundary.
- Reset returns the demo to a known clean state.
- Sage can visually explain key PlotPickle concepts without receiving extra authority.
- "Make This Mine" crosses into the existing real profile boundary explicitly and copies only approved content.
- Returning-user startup remains understandable and does not force DEMO.
- README reflects the shipped behavior.
- Windows installer compiles and passes packaged interaction plus install/uninstall smoke tests.
Non-goals
- no replacement of the local profile/auth system;
- no anonymous public cloud account;
- no weakening of PPF canon authority;
- no second STORY engine;
- no second knowledge graph;
- no arbitrary external browsing or provider setup inside DEMO;
- no turning Guest into a general unauthenticated application session.
Primary risk
Risk: DEMO gradually becomes a second, partially privileged PlotPickle runtime and duplicates product logic.
Mitigation: DEMO must use the same production STORY/PPF projections and UI components through a synthetic data adapter, while keeping a distinct disposable storage/authority boundary. If a feature requires a duplicate engine or special privileged path, it does not belong in DEMO.
Current developer brief
Developer Workbench review: 2026-09-07T20:53:58.1565609+00:00
Reviewed exact PR head: 805437548b92b01d804fceac118644f35090b046
{
"file": {
"name": "scripts/windows-installer/demo-onboarding-smoke.mjs",
"diff": {
"from": "index 00cfb5a61..15281d26a 100644\n--- a/scripts/windows-installer/demo-onboarding-smoke.mjs\n+++ b/scripts/windows-installer/demo-onboarding-smoke.mjs\n@@ -13,7 +13,7 @@ const root = path.resolve(process.argv[2] ?? \".\");\n const reportDirectory = path.resolve(process.argv[3] ?? path.join(root, \"reports\", \"windows-demo-onboarding\"));\n const totalTimeoutMs = Math.min(Number(process.env.PLOTPICKLE_DEMO_SMOKE_TIMEOUT_MS || 6 * 60_000), 6 * 60_000);\n const actionTimeoutMs = 20_000;\n-const routeTimeoutMs = 45_000;\n+const routeTimeoutMs = 90_000;\n const coldDemoTimeoutMs = 90_000;\n const coldProfileTimeoutMs = 120_000;\n const deadline = Date.now() + totalTimeoutMs;",
"to": "index 2a3760bd9..2a3760bd9 100644\n--- a/scripts/windows-installer/demo-onboarding-smoke.mjs\n+++ b/scripts/windows-installer/demo-onboarding-smoke.mjs\n@@ -13,7 +13,8 @@ const root = path.resolve(process.argv[2] ?? \".\");\n const reportDirectory = path.resolve(process.argv[3] ?? path.join(root, \"reports\", \"windows-demo-onboarding\"));\n const totalTimeoutMs = Math.min(Number(process.env.PLOTPICKLE_DEMO_SMOKE_TIMEOUT_MS || 6 * 60_000), 6 * 60_000);\n const actionTimeoutMs = 20_000;\n-const routeTimeoutMs = 90_000;\n+const routeTimeoutMs = 45_000;\n+const navigationCommandTimeoutMs = 10_000;\n const coldDemoTimeoutMs = 90_000;\n const coldProfileTimeoutMs = 120_000;\n const deadline = Date.now() + totalTimeoutMs;\n@@ -263,8 +264,18 @@ async function browserState(client) {\n }\n \n async function navigate(client, url) {\n- await client.send(\"Page.navigate\", { url }, routeTimeoutMs);\n- await waitFor(client, `document.readyState !== \"loading\" && Boolean(document.body)`, `Navigation to ${url}`);\n+ try {\n+ await client.send(\"Page.navigate\", { url }, navigationCommandTimeoutMs);\n+ } catch (error) {\n+ const message = error instanceof Error ? error.message : String(error);\n+ if (!/^Page\\.navigate exceeded \\d+ ms\\.$/u.test(message)) throw error;\n+ }\n+ await waitFor(\n+ client,\n+ `location.href.startsWith(${JSON.stringify(url)}) && document.readyState !== \"loading\" && Boolean(document.body)`,\n+ `Navigation to ${url}`,\n+ routeTimeoutMs,\n+ );\n }\n \n async function installSessionCookie(client, baseUrl, cookieHeader) {"
}
}
}
Goal
Create a first-run DEMO experience that lets a new Human understand and try PlotPickle before creating or unlocking a private local profile.
This should reduce onboarding friction without weakening the existing profile, Guest, storage, canon or agent-authority boundaries.
Why
PlotPickle is easier to understand by experiencing it than by reading a feature list. A new user should be able to launch the app, choose DEMO, interact with a small synthetic story world, see how decisions propagate, and then decide whether to create a real local profile.
The experience should borrow the useful idea behind HumanLayer's
show-meskill: explain the current thing visually with the smallest useful view. For PlotPickle, this becomes a bounded Sage capability rather than a dependency on HumanLayer.Refs #1675 and the existing profile/auth work in #1143.
First-run shape
On a fresh installation, present two clear choices:
DEMO � See PlotPickle work
ENTER PLOTPICKLE � Create or unlock your private profile
For returning users with an existing local profile, normal startup may continue to favor the profile chooser while keeping DEMO available as a secondary action.
Architectural boundary
DEMO must be a separate bounded runtime. It must NOT silently expand the current Guest surface.
Existing Guest behavior remains unchanged: ephemeral notes only, no mounting of the private PlotPickle application, and deletion on exit.
DEMO may mount a curated application experience only against synthetic/demo-owned data. It must have no read/write access to:
Demo content
Start with one small prepared STORY scenario, ideally 5–10 minutes.
The demo should visibly demonstrate a few uniquely PlotPickle behaviors rather than expose every workspace:
Sage "Show Me" capability
Add a narrow explanatory mode for Sage that selects the smallest useful visual representation for the current concept.
Possible views:
This is an explanatory projection only. It must not gain new write authority or bypass existing trust/canon checks.
Conversion into the real product
At natural points, DEMO may offer Make This Mine / Start My Story.
That action must:
A user must also be able to exit DEMO with no profile creation and no retained private state.
Suggested phases
Phase 0 � Contract and boundary
Phase 1 � Deterministic demo world
Phase 2 � First-run UI
Phase 3 � Sage Show Me
Phase 4 � Make This Mine
Phase 5 � UAT and packaging
PlotPickleSetup.exebefore release readiness.Acceptance criteria
Non-goals
Primary risk
Risk: DEMO gradually becomes a second, partially privileged PlotPickle runtime and duplicates product logic.
Mitigation: DEMO must use the same production STORY/PPF projections and UI components through a synthetic data adapter, while keeping a distinct disposable storage/authority boundary. If a feature requires a duplicate engine or special privileged path, it does not belong in DEMO.
Current developer brief
Developer Workbench review: 2026-09-07T20:53:58.1565609+00:00
Reviewed exact PR head:
805437548b92b01d804fceac118644f35090b046{ "file": { "name": "scripts/windows-installer/demo-onboarding-smoke.mjs", "diff": { "from": "index 00cfb5a61..15281d26a 100644\n--- a/scripts/windows-installer/demo-onboarding-smoke.mjs\n+++ b/scripts/windows-installer/demo-onboarding-smoke.mjs\n@@ -13,7 +13,7 @@ const root = path.resolve(process.argv[2] ?? \".\");\n const reportDirectory = path.resolve(process.argv[3] ?? path.join(root, \"reports\", \"windows-demo-onboarding\"));\n const totalTimeoutMs = Math.min(Number(process.env.PLOTPICKLE_DEMO_SMOKE_TIMEOUT_MS || 6 * 60_000), 6 * 60_000);\n const actionTimeoutMs = 20_000;\n-const routeTimeoutMs = 45_000;\n+const routeTimeoutMs = 90_000;\n const coldDemoTimeoutMs = 90_000;\n const coldProfileTimeoutMs = 120_000;\n const deadline = Date.now() + totalTimeoutMs;", "to": "index 2a3760bd9..2a3760bd9 100644\n--- a/scripts/windows-installer/demo-onboarding-smoke.mjs\n+++ b/scripts/windows-installer/demo-onboarding-smoke.mjs\n@@ -13,7 +13,8 @@ const root = path.resolve(process.argv[2] ?? \".\");\n const reportDirectory = path.resolve(process.argv[3] ?? path.join(root, \"reports\", \"windows-demo-onboarding\"));\n const totalTimeoutMs = Math.min(Number(process.env.PLOTPICKLE_DEMO_SMOKE_TIMEOUT_MS || 6 * 60_000), 6 * 60_000);\n const actionTimeoutMs = 20_000;\n-const routeTimeoutMs = 90_000;\n+const routeTimeoutMs = 45_000;\n+const navigationCommandTimeoutMs = 10_000;\n const coldDemoTimeoutMs = 90_000;\n const coldProfileTimeoutMs = 120_000;\n const deadline = Date.now() + totalTimeoutMs;\n@@ -263,8 +264,18 @@ async function browserState(client) {\n }\n \n async function navigate(client, url) {\n- await client.send(\"Page.navigate\", { url }, routeTimeoutMs);\n- await waitFor(client, `document.readyState !== \"loading\" && Boolean(document.body)`, `Navigation to ${url}`);\n+ try {\n+ await client.send(\"Page.navigate\", { url }, navigationCommandTimeoutMs);\n+ } catch (error) {\n+ const message = error instanceof Error ? error.message : String(error);\n+ if (!/^Page\\.navigate exceeded \\d+ ms\\.$/u.test(message)) throw error;\n+ }\n+ await waitFor(\n+ client,\n+ `location.href.startsWith(${JSON.stringify(url)}) && document.readyState !== \"loading\" && Boolean(document.body)`,\n+ `Navigation to ${url}`,\n+ routeTimeoutMs,\n+ );\n }\n \n async function installSessionCookie(client, baseUrl, cookieHeader) {" } } }