@foldspace_npm/harness 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CLAUDE.md +402 -204
  2. package/README.md +84 -36
  3. package/bin/ask.mjs +19 -9
  4. package/bin/attach.mjs +91 -56
  5. package/bin/badge.mjs +28 -8
  6. package/bin/cli.mjs +23 -6
  7. package/bin/inject.mjs +3 -4
  8. package/bin/observe.mjs +22 -6
  9. package/bin/published.mjs +141 -0
  10. package/package.json +6 -2
  11. package/recipes/INDEX.md +13 -8
  12. package/recipes/README.md +30 -7
  13. package/recipes/account-overview/README.md +6 -6
  14. package/recipes/account-overview/agent/actions/show_account_overview.ts +11 -16
  15. package/recipes/account-overview/agent/views/brand.ts +23 -14
  16. package/recipes/account-overview/agent/views/overview.ts +13 -17
  17. package/recipes/bottom-bar/README.md +12 -9
  18. package/recipes/bottom-bar/agent/bottomBar.ts +14 -12
  19. package/recipes/create-a-record/README.md +97 -0
  20. package/recipes/create-a-record/agent/actions/create_project.ts +26 -0
  21. package/recipes/create-a-record/agent/api/projects.ts +62 -0
  22. package/recipes/create-a-record/agent/projects.ts +145 -0
  23. package/recipes/create-a-record/agent/views/approval.ts +181 -0
  24. package/recipes/create-a-record/agent/views/brand.ts +35 -0
  25. package/recipes/create-a-record/agent/views/fields.ts +252 -0
  26. package/recipes/create-a-record/agent/views/project-card.ts +253 -0
  27. package/recipes/create-a-record/agent/views/steps.ts +92 -0
  28. package/recipes/create-a-record/fixtures/owners.ok.json +9 -0
  29. package/recipes/create-a-record/fixtures/project.created.json +1 -0
  30. package/recipes/create-a-record/fixtures/project.rejected.json +1 -0
  31. package/recipes/create-a-record/recipe.json +13 -0
  32. package/recipes/create-a-record/variants/actions/create_project.steps.ts +160 -0
  33. package/recipes/find-by-name/README.md +3 -1
  34. package/recipes/find-by-name/agent/actions/find_project_id.ts +1 -1
  35. package/recipes/lookup-then-view/README.md +73 -0
  36. package/recipes/lookup-then-view/agent/accounts.ts +174 -0
  37. package/recipes/lookup-then-view/agent/actions/find_account.ts +108 -0
  38. package/recipes/lookup-then-view/agent/actions/show_account_overview.ts +42 -0
  39. package/recipes/lookup-then-view/agent/api/accounts.ts +68 -0
  40. package/recipes/lookup-then-view/agent/views/account.ts +183 -0
  41. package/recipes/lookup-then-view/agent/views/brand.ts +35 -0
  42. package/recipes/lookup-then-view/agent/views/format.ts +73 -0
  43. package/recipes/lookup-then-view/agent/views/pick-one.ts +240 -0
  44. package/recipes/lookup-then-view/fixtures/accounts.json +319 -0
  45. package/recipes/lookup-then-view/fixtures/overview.ok.json +122 -0
  46. package/recipes/lookup-then-view/fixtures/overview.unsigned.json +14 -0
  47. package/recipes/lookup-then-view/recipe.json +11 -0
  48. package/recipes/lookup-then-view/studio/find_account.json +10 -0
  49. package/recipes/lookup-then-view/studio/show_account_overview.json +15 -0
  50. package/recipes/opportunities-at-risk/README.md +5 -5
  51. package/recipes/opportunities-at-risk/agent/actions/show_opportunities_at_risk.ts +9 -7
  52. package/recipes/opportunities-at-risk/agent/opportunities.ts +11 -10
  53. package/recipes/opportunities-at-risk/agent/views/at-risk.ts +1 -1
  54. package/recipes/opportunities-at-risk/agent/views/brand.ts +26 -15
  55. package/recipes/pick-from-a-list/README.md +7 -6
  56. package/recipes/pick-from-a-list/agent/actions/choose_project.ts +27 -12
  57. package/recipes/pick-from-a-list/agent/views/brand.ts +30 -9
  58. package/recipes/pick-from-a-list/agent/views/picker.ts +3 -3
  59. package/recipes/prepare-for-a-meeting/README.md +4 -4
  60. package/recipes/prepare-for-a-meeting/agent/actions/prepare_for_meeting.ts +3 -3
  61. package/recipes/records-list/README.md +6 -6
  62. package/recipes/records-list/agent/accounts.ts +3 -1
  63. package/recipes/records-list/agent/actions/list_accounts.ts +7 -7
  64. package/recipes/records-list/agent/views/brand.ts +26 -14
  65. package/recipes/server-rendered-forms/README.md +35 -0
  66. package/recipes/server-rendered-forms/agent/actions/create_record_via_form.ts +62 -0
  67. package/recipes/server-rendered-forms/agent/api/pages.ts +57 -0
  68. package/recipes/server-rendered-forms/agent/forms.ts +192 -0
  69. package/recipes/server-rendered-forms/fixtures/form.errors.html +9 -0
  70. package/recipes/server-rendered-forms/fixtures/form.html +17 -0
  71. package/recipes/server-rendered-forms/fixtures/list.html +9 -0
  72. package/recipes/server-rendered-forms/fixtures/record.html +4 -0
  73. package/recipes/server-rendered-forms/recipe.json +10 -0
  74. package/recipes/several-hosts/README.md +10 -13
  75. package/recipes/several-hosts/agent/hosts.ts +19 -136
  76. package/recipes/several-hosts/fixtures/wrong-page.json +6 -0
  77. package/recipes/several-hosts/recipe.json +3 -3
  78. package/recipes/swap-the-login-method/README.md +16 -5
  79. package/recipes/swap-the-login-method/agent/utils.ts +52 -3
  80. package/recipes/swap-the-login-method/variants/utils.cookies.ts +59 -6
  81. package/recipes/update-meeting-notes/README.md +10 -4
  82. package/recipes/update-meeting-notes/agent/actions/update_meeting_notes.ts +16 -5
  83. package/recipes/upload-contacts/README.md +5 -5
  84. package/recipes/upload-contacts/agent/actions/upload_contacts.ts +7 -7
  85. package/recipes/upload-contacts/agent/views/brand.ts +24 -14
  86. package/recipes/upload-contacts/agent/views/uploader.ts +28 -17
  87. package/recipes/who-is-the-user/README.md +7 -3
  88. package/src/ask-core.mjs +16 -4
  89. package/src/attach-daemon.mjs +9 -2
  90. package/src/attach-helpers.mjs +105 -0
  91. package/src/attach-preflight.mjs +34 -11
  92. package/src/badge-core.mjs +1 -1
  93. package/src/cdp-client.mjs +15 -19
  94. package/src/cdp-ownership.mjs +41 -20
  95. package/src/cli-help.mjs +7 -5
  96. package/src/cli-registry.mjs +102 -47
  97. package/src/diagnostics.mjs +1 -1
  98. package/src/init.mjs +5 -3
  99. package/src/keep-focus.mjs +17 -13
  100. package/src/lint/index.mjs +8 -1
  101. package/src/lint/rules.mjs +240 -43
  102. package/src/observe-core.mjs +163 -34
  103. package/src/published-core.mjs +392 -0
  104. package/src/region.mjs +1 -1
  105. package/src/runtime/agent.ts +16 -12
  106. package/src/runtime/http.ts +25 -15
  107. package/src/runtime/match.ts +1 -1
  108. package/src/runtime/render.ts +4 -4
  109. package/src/transports/README.md +2 -2
  110. package/src/upgrade.mjs +33 -10
  111. package/templates/agent-starter/CLAUDE.md +8 -5
  112. package/templates/agent-starter/README.md +32 -33
  113. package/templates/agent-starter/agent/actions/_example.ts +4 -4
  114. package/templates/agent-starter/agent/actions/index.ts +2 -2
  115. package/templates/agent-starter/agent/constants.ts +4 -3
  116. package/templates/agent-starter/agent/utils.ts +15 -18
  117. package/templates/agent-starter/claude-settings.json +2 -1
  118. package/templates/agent-starter/docs/app-profile.md +19 -14
  119. package/templates/agent-starter/foldspace.dev.json +1 -1
  120. package/recipes/several-hosts/fixtures/nav-record.json +0 -5
package/CLAUDE.md CHANGED
@@ -4,23 +4,24 @@ You are adding an AI agent to a product that already exists. It answers
4
4
  questions and *does things* inside that product, with the user's own session.
5
5
 
6
6
  The person you are working with may not be a developer. **You run the
7
- commands; they sign in and talk to the agent.** Their first session ends at
8
- something they can **see on their own page** — not at "it builds".
7
+ commands; they sign in, talk to the agent, and say yes or no.** Their first
8
+ session ends at something they can **see on their own page** - not at
9
+ "it builds".
9
10
 
10
11
  ## Every session starts the same way
11
12
 
12
13
  1. **Harness version.** Run `npx foldspace help --json` (from an older install,
13
14
  `npx --yes @foldspace_npm/harness@latest upgrade --check`). If
14
- `update.outdated` is true, say the pinned and latest versions and **ask
15
- before upgrading**; then `foldspace upgrade --yes` and `npm run build`. Never
16
- upgrade as a side effect of build, attach or implementing an action.
17
- `build` and `attach` check too: behind prints one line and carries on; a
18
- version Foldspace **withdrew** refuses to run and says why. Tell the user
19
- the reason and ask to upgrade. Only if they decide not to, rerun with
20
- `FOLDSPACE_SKIP_UPDATE_CHECK=1` - never set it on your own. If the
21
- project `CLAUDE.md` does not contain
15
+ `update.outdated` is true, say which version the project is on and which is
16
+ newest, and **ask before upgrading**; then `npx foldspace upgrade --yes` and
17
+ `npm run build`. Never upgrade as a side effect of build, attach or
18
+ implementing an action. `build` and `attach` check too: on an older version
19
+ they print one line and carry on; on a version Foldspace **withdrew** they
20
+ refuse to run and say why. Tell the human the reason and ask to upgrade.
21
+ Only if they decide not to, rerun with `FOLDSPACE_SKIP_UPDATE_CHECK=1` -
22
+ never set it on your own. If the project `CLAUDE.md` does not contain
22
23
  `@node_modules/@foldspace_npm/harness/CLAUDE.md`, ask before
23
- `foldspace upgrade --refresh-instructions`.
24
+ `npx foldspace upgrade --refresh-instructions`.
24
25
  2. **Is Foldspace connected? Check — never assume.** `region` in
25
26
  `foldspace.dev.json` (absent means `us`) picks the server: `foldspace`, or
26
27
  `foldspace-eu` for `eu`. Use that server's tools all session. Call its
@@ -28,8 +29,8 @@ something they can **see on their own page** — not at "it builds".
28
29
 
29
30
  | Result | Do |
30
31
  |---|---|
31
- | The tool does not exist | The Foldspace MCP is not installed in this editor. Give the one install step for *this* region and editor — **EU**, any editor: the MCP block on <https://foldspace.ai/eu/agents.md>, never the plugin. **US** — Claude Code: `claude plugin install foldspace-agent-builder@foldspace-plugins`; anything else: the MCP block on <https://foldspace.ai/agents.md>. Then check again |
32
- | It exists but fails on auth | Installed, not signed in. Only the human can approve sign-in. Tell them exactly where — Claude Code: `/mcp` → **foldspace-eu** (**EU**) or **foldspace** (**US**) → finish in the browser — then check again |
32
+ | The tool does not exist | The Foldspace MCP is not installed in this editor. Install it yourself, the one way that fits *this* region and editor. **EU** - any editor: the MCP block on <https://foldspace.ai/eu/agents.md>, never the plugin. **US** - Claude Code with git on the machine (`command -v git`): `claude plugin marketplace add https://github.com/foldspace-ai/plugins` (already added, or added from another source, is fine), then `claude plugin install foldspace-agent-builder@foldspace-plugins`; without git, or any other editor: the MCP block on <https://foldspace.ai/agents.md>. Either way it needs sign-in: start it before your turn ends, as **Connect Foldspace tools** on that page says. **The plugin** loads when your turn ends: call `list_agents` again on the human's next message. **A server added from the MCP block** (`claude mcp add`) never reaches a chat that is already running - not on the next message, not through `/mcp`: once it is signed in, the human opens a new chat in this folder and repeats their request. `claude mcp get` saying Connected proves only the setting; the check is `list_agents` in the chat you are in |
33
+ | It exists but fails on auth | Installed, not signed in. Only the human can approve sign-in. Tell them exactly where - Claude Code: `/mcp` → **foldspace-eu** (**EU**) or **foldspace** (**US**; listed as `plugin:foldspace-agent-builder:foldspace` when the plugin installed it) → finish in the browser - then check again. If `/mcp` does not list that server, it was added after this chat started: the human opens a new chat in this folder, as above |
33
34
  | It returns agents | One agent: state its name, Agent Key and product in a line and carry on; the human objects if it is wrong. Several: ask which, as a choice |
34
35
 
35
36
  3. **Orient** — `get_agent_settings`, `list_actions`, `list_task_agents`,
@@ -38,6 +39,39 @@ something they can **see on their own page** — not at "it builds".
38
39
  seeded at sign-up are often a specification someone already wrote.
39
40
  4. **Read `docs/app-profile.md`.** It is what is already known about this app,
40
41
  with how each fact was established.
42
+ 5. **Does the agent already have code saved in Foldspace?** If your Foldspace
43
+ tools include `repo_read`, call it with command `status` now - before the
44
+ first build, because the first build is what the test window shows them.
45
+ - **A code project.** Call `repo_read` with command `instructions` and
46
+ follow what it returns. The project's `agent/` is the copy Foldspace
47
+ builds and publishes, and it may hold work someone else saved. Before you
48
+ build, replace this folder's `agent/` with it (`repo_read` commands `tree`
49
+ and `file`): delete what is there rather than copying over the top, or
50
+ their page shows the template instead of their agent. An action file here
51
+ that the project lacks is work a session never saved: keep it aside and
52
+ put it back after.
53
+ - **`NO_REPO`.** Nothing is saved yet. Follow the next step `status` gives:
54
+ it creates the code project in about a minute and changes nothing anyone
55
+ sees - say you are setting up their project, no need to ask. Get the agent
56
+ live in this folder meanwhile (L0 needs no code). Once `status` shows the
57
+ project, replace this folder's `agent/` with its `agent/` as above, before
58
+ you write the first action.
59
+ - **A refusal that names `foldspace-ai-access`.** This agent has saved
60
+ code that Foldspace has not opened to these tools; that is final. Tell
61
+ the human that Foldspace manages their agent's saved code, so this build
62
+ starts fresh.
63
+ - **A refusal that starts "Your Foldspace account cannot".** It is about
64
+ the signed-in account, so you cannot tell whether code is saved. Pass its
65
+ words on as they are - they are written for the human.
66
+ - **Anything else** - `status` says a project claims this agent but cannot
67
+ be used, or another error. Create none. Tell the human in your own words
68
+ that their agent's saved code cannot be used right now; never pass on the
69
+ text that came back - it can name things they cannot open.
70
+
71
+ After any of these three, build in this folder, never stop the session
72
+ over it, and tell the human once that the code stays on this computer.
73
+ - **No `repo_read` tool.** This folder is the only copy, so there is no code
74
+ project.
41
75
 
42
76
  Before an unfamiliar harness operation, `npx foldspace help --json` is the
43
77
  current CLI contract — risk, prerequisites, effects, next step.
@@ -47,14 +81,15 @@ current CLI contract — risk, prerequisites, effects, next step.
47
81
  | Level | Reached when |
48
82
  |---|---|
49
83
  | **L0 · agent live** | injected, visible on the page |
50
- | **L1 · read settings** | the user's own account, plan or settings in a widget, showing their real values |
84
+ | **L1 · read settings** | the user's own account, plan or settings on a card, showing their real values |
51
85
  | **L2 · look up entities** | a search from the user's own words, showing rows |
52
86
 
53
- 1. **Get the agent live first (L0) - with no actions.** Straight after
54
- orienting: `npm run build`, `npm run inject`, and one ask - *"A Chrome
55
- window opened at <Product>'s login. Please log in there - I'll notice when
56
- you're in."* Then `npx foldspace observe wait-login`: it returns the moment
57
- they are in, so nobody has to type "I've logged in". Then
87
+ 1. **Get the agent live first (L0) - with no actions.** Straight after the
88
+ steps above (with a code project, once its `agent/` is in place - step 5):
89
+ `npm run build`, `npm run inject`, and one ask - *"A Chrome window opened at
90
+ <Product>'s login. Please log in there - I'll notice when you're in."* Then
91
+ `npx foldspace observe wait-login`: it returns the moment they are in, so
92
+ nobody has to type "I've logged in". Then
58
93
  `npx foldspace attach --daemon` - and if it answers that the page has no
59
94
  Foldspace code, `npx foldspace attach --bootstrap --daemon` (an app that
60
95
  never had Foldspace). An empty registry is valid. L0 is
@@ -69,33 +104,37 @@ current CLI contract — risk, prerequisites, effects, next step.
69
104
  2. **Then offer experiences - from what you already have.** With the agent on
70
105
  their screen, offer three or four read-only experiences as a choice,
71
106
  recommended first. Sources: the product's own menu (`npx foldspace observe
72
- menu` takes a second and moves nothing), the conversation starters seeded at sign-up,
73
- `discover_actions`, and `docs/app-profile.md`. `discover_actions` gives
74
- candidates, not an inventory. Do not investigate the app to build this
107
+ menu` takes a second and moves nothing), the conversation starters seeded
108
+ at sign-up, `discover_actions`, and `docs/app-profile.md`. `discover_actions`
109
+ gives candidates, not an inventory. Do not investigate the app to build this
75
110
  list. Order them cheapest first: the user's own data with no parameter
76
111
  (L1) before a search with one parameter (L2).
77
112
  3. **Only then observe - and only what the chosen experience needs** (**Find
78
113
  the call yourself**). Check the row count as part of that: an experience
79
114
  on a resource the account has zero of is a demo of an empty state, so say
80
- so and offer the next one. On an empty account the useful first action is
81
- one that creates data - say so instead.
82
-
83
- **Say when the test window is yours, and when it is theirs - on the page and
84
- in chat.** You and the human share one Chrome window. `observe` and `ask`
85
- raise the working pill by themselves for as long as they run, and drop it
86
- after. For the longer stretch - from the moment you start working on their
87
- window until it is their turn - run `npx foldspace badge working` (the pill at
88
- the foot of the window says Foldspace is building here, leave it alone) and say
89
- so in chat: *"I'm going to work in that Chrome window for a few minutes -
90
- please leave it alone until I tell you it's ready."* When it is their turn,
91
- `npx foldspace badge ready` takes the pill away (the small corner tag comes
92
- back) and in chat you say what to try. No bar for their turn: they may type
93
- whatever, whenever; the pill only ever asks them to wait. `foldspace badge label` puts the small
94
- corner tag back; `off` removes it, for a screenshot they will share.
95
-
96
- **After they have tried it, offer the second wave - one choice, recommended
97
- first, not four questions.** Once you have handed over (`badge ready`) and the
98
- human has asked the agent the question:
115
+ so and offer the next one. When the account has nothing to read at all,
116
+ say so and offer a write first: a card that creates a record and writes
117
+ only when the user approves it (`recipes/create-a-record`).
118
+
119
+ **Say when the test window is yours, and when it is theirs - on the page and in
120
+ chat.** You and the human share one Chrome window. `ask` and `observe` (except
121
+ `screenshot`, `pages` and `wait-login`) raise the working pill by themselves for
122
+ as long as they run, and drop it after. For the longer stretch - from the moment
123
+ you start working on their window until it is their turn - run
124
+ `npx foldspace badge working` (the pill at the foot of the window says Foldspace
125
+ is building here, leave it alone) and say so in chat: *"I'm going to work in
126
+ that Chrome window for a few minutes - please leave it alone until I tell you
127
+ it's ready."* When it is their turn, `npx foldspace badge ready` takes the pill
128
+ away (the small corner tag comes back) and in chat you say what to try. Nothing
129
+ marks their turn: they may type whatever, whenever; the pill only ever asks them
130
+ to wait. `npx foldspace badge label` puts the small corner tag back;
131
+ `npx foldspace badge off` removes it, for a screenshot they will share.
132
+
133
+ **After they have tried it, the second wave: tell Foldspace who the user is
134
+ (item 3 - no question, one line in chat), and offer the rest as one choice,
135
+ recommended first.** Once you have handed over (`badge ready`), the human has
136
+ asked the agent the question and - with a code project - you have taken the
137
+ steps in **Done, and what you say at the end**:
99
138
 
100
139
  1. **Starters.** The sign-up starters point at actions the agent does not have
101
140
  (clicking one answers "that action isn't available"): replace them with the
@@ -106,52 +145,70 @@ human has asked the agent the question:
106
145
  project's start-up code; nothing in their source changes.
107
146
  3. **Tell Foldspace who the user is** - `recipes/who-is-the-user`: from then on
108
147
  their conversations and analytics stop being anonymous, and the agent has
109
- real-time context about who it is talking to. One line in chat, no question.
110
- 4. **A second experience** - and, once a card shows records, navigation to a
111
- record's page.
148
+ real-time context about who it is talking to.
149
+ 4. **A second experience** - a read, or a write through
150
+ `recipes/create-a-record` - and, once a card shows records, navigation to
151
+ a record's page.
152
+ 5. **No emoji in any of the agent's replies** - one line in the agent's
153
+ Behavior instructions (Agent Studio → **Style & Behavior** → **Behavior &
154
+ Responses**). No Foldspace tool writes those, so it is theirs to add: on a
155
+ yes, give them the line to paste and where it goes.
112
156
 
113
157
  Not before the first experience: nothing about it needs any of these, and it
114
158
  is silent work before the human has seen anything.
115
159
 
116
- Uploads, extraction and anything that writes are a later session.
160
+ Uploads and extraction are a later session.
117
161
 
118
162
  ### Ask as a choice, and ask almost nothing
119
163
 
120
164
  Every question to the human is an **enumerated choice with the recommended
121
- option first** (in Claude Code, `AskUserQuestion`). A first session has four
122
- decision points: which agent (only if several), the app's hostname, which
123
- experience first, and whether to publish. The hostname is the only free-text
124
- question. Everything else — attach mode, folder, name, level — you **state and
125
- move on**.
126
-
127
- Then agree the experience in the customer's words, before creating anything:
165
+ option first** (in Claude Code, `AskUserQuestion`). A first session asks only
166
+ this: which agent (only if several), the app's hostname, which experience
167
+ first - and a yes wherever this manual asks for one, such as agreeing the
168
+ experience (below), upgrading, publishing an action, task agent or
169
+ navigation, publishing their code for their users, replacing their starters or
170
+ adding hosts. The hostname is the only free-text question. Everything else -
171
+ attach mode, folder, name, level - you **state and move on**.
172
+
173
+ Then agree the experience in the human's words, before creating anything:
128
174
  *"Show my plan and credits needs one new action on your agent and one card to
129
175
  show it in. Sound right?"*
130
176
 
131
177
  ## One product, several hosts
132
178
 
133
- Many products live on more than one host - `www.`, `analyze.`, `admin.` under
134
- one domain. `init` covers only the host it was given, so on the others the
135
- agent does not appear and every command refuses the window.
179
+ Many products live on more than one host - `www.`, `app.`, `reports.`,
180
+ `admin.` under one domain. `init` covers the host it was given and that
181
+ host's subdomains, so on sibling hosts the agent does not appear and every
182
+ command refuses the window.
136
183
 
137
184
  - **Find out at the start.** Straight after the agent is live, `npx foldspace
138
185
  observe menu` lists the hosts the page links to that belong to the same
139
186
  product and that this project does not cover (`otherHosts`). `observe pages`
140
187
  and the attach log say the same when the window lands on one.
141
188
  - **Widening is the human's yes.** Name the hosts you found and ask once:
142
- *"Your product also lives on analyze.… and communicate.…. Add them so the
143
- agent works there too?"* Then add the one pattern (`"*.example.com"`) to
144
- `hosts` in `foldspace.dev.json`, `npm run inject`, and attach again.
189
+ *"Your product also lives on reports.… and admin.…. Add them so the agent
190
+ works there too?"* Then add the one pattern (`"*.example.com"`) to
191
+ `hosts` in `foldspace.dev.json`, then `npx foldspace attach --stop`,
192
+ `npm run inject`, and attach again with the same flags (**Local loop**).
145
193
  - **Then go there yourself.** `npx foldspace observe screen --goto
146
- https://analyze.example.com/reports` opens a screen on any host the project
194
+ https://reports.example.com/` opens a screen on any host the project
147
195
  names. Do not ask the human to click a tile you can open.
148
- - **An action reads only the host it runs on** - that is the browser. Build
149
- each experience on the host its data lives on, and use
196
+ - **An action reads only what the page it runs on may read** - that is the
197
+ browser: another host's signed-in data is refused unless that host allows
198
+ this one. Build each experience on the host its data lives on, and use
150
199
  `recipes/several-hosts`: an action asked from the wrong host answers
151
200
  `wrong_page` instead of failing.
152
- - **The user and their conversations carry across hosts; the agent carrying
153
- on after it navigated does not** (PLG-5962). `recipes/several-hosts` has the
154
- workaround. It is a workaround: register it (**Rules**, 9).
201
+ - **The user, their conversations and the agent's turn all carry across
202
+ hosts.** The SDK keeps its resume note for 60 seconds in a cookie on the
203
+ domain the hosts share (`example.com`), so an agent that moved the user to
204
+ another host in the same tab carries on there. Only the navigation's own
205
+ result goes with it: another action that ran in the same turn reaches the
206
+ agent as "not available". Build nothing for the hand-over:
207
+ `recipes/several-hosts` is only for the wrong-host answer above. If the
208
+ project already has `installResumeAcrossHosts` (a row under
209
+ **Workarounds** in `docs/app-profile.md`), remove it as that recipe says:
210
+ with both, the user sees an extra message and the agent takes a second
211
+ turn.
155
212
 
156
213
  ## Find the call yourself
157
214
 
@@ -176,16 +233,16 @@ and asking are not the only options — **observing is, and it comes first**:
176
233
  npx foldspace observe screen --click "<a label from menu>" --match "<three to five words for the data>"
177
234
  npx foldspace observe read "<the GET path screen returned>" # GET only: status, field names and types, row count
178
235
  npx foldspace observe auth # only if a read came back 401 or 403
179
- npx foldspace observe styles # their font and colours, for the card
236
+ npx foldspace observe styles --write # agent/views/brand.ts for the cards, before the first card
180
237
  ```
181
238
 
182
239
  `screen` reaches a screen the way a person does and returns at most eight
183
- requests, ranked by your words - field names, value types and row counts,
184
- **never values**. `read` confirms the one you picked. `auth` reports *where*
185
- each header value comes from, never the value: do not go hunting through
186
- storage, cookies or bundles yourself. One experience needs one screen; two
187
- `screen` calls is normal, five means the experience is wrong for this
188
- product - offer the next one.
240
+ requests, ranked by your words - field names and row counts, **never
241
+ values**. `read` confirms the one you picked, with value types. `auth`
242
+ reports *where* each header value comes from, never the value: do not go
243
+ hunting through storage, cookies or bundles yourself. One experience needs
244
+ one screen; two `screen` calls is normal, five means the experience is wrong
245
+ for this product - offer the next one.
189
246
 
190
247
  **`observe` refuses what is not provably looking**, and a refusal is an
191
248
  answer (**Safety**): it follows links, tabs and menu openers only - a plain
@@ -197,7 +254,8 @@ and asking are not the only options — **observing is, and it comes first**:
197
254
  an MFA step, a paywall, a screen that needs a record the account does not
198
255
  have, or anything that would write.
199
256
 
200
- Signing in is the human's step, because `inject` launches a fresh profile.
257
+ Signing in is the human's step, because `inject` launches its own Chrome
258
+ profile (empty the first time; it keeps the sign-in after that).
201
259
  Navigating is yours. **While you drive a signed-in account you are read-only:**
202
260
  GET only, never submit a form, never click anything that creates, sends or
203
261
  deletes.
@@ -209,11 +267,11 @@ an assumption by quoting that file later.
209
267
 
210
268
  ## Build: objects first, code second
211
269
 
212
- Four kinds of thing. With the customer, use **Foldspace's own words** for what
270
+ Four kinds of thing. With the human, use **Foldspace's own words** for what
213
271
  they will see in Agent Studio - **action**, **task agent**, **navigation**,
214
272
  **publish** - and say what each does the first time you use it. Never invent a
215
273
  substitute: no "skill", "ability", "capability" or "tool", and no "switch on"
216
- for publish. "Skill" already means something else in Claude, and the customer
274
+ for publish. "Skill" already means something else in Claude, and the human
217
275
  has seen "Actions" at sign-up and in Agent Studio - a new word breaks the link
218
276
  to what they know. The action handler is yours: call it "the code behind it".
219
277
 
@@ -229,7 +287,15 @@ built-ins — no code, and a first experience can lean on them.
229
287
 
230
288
  1. `create_action` as a **draft**. `generate_action_handler` works from a draft
231
289
  schema; do not publish yet. Generating the handler from the schema is what
232
- keeps the two from drifting.
290
+ keeps the two from drifting. **Declare every parameter fully**: type,
291
+ `required`, and a description that says where the value comes from. That
292
+ schema is what the agent reads to wire actions together by itself - a
293
+ required `accountId` described as "get it from `find_account`" makes it
294
+ call `find_account` first. Showing the one match without asking, and a
295
+ searchable list when there are several or none, come from `find_account`'s
296
+ instructions and code (`recipes/lookup-then-view`). An id marked optional,
297
+ or described only as "the id", leaves the agent guessing, or asking the end
298
+ user for an id they do not have.
233
299
  2. Implement the handler from the observed request and response shapes. A
234
300
  handler is `fetch` plus `runTask` and nothing more. Import helpers from
235
301
  `agent/utils.ts` — inspect that file before writing another `fetch` or rank
@@ -239,29 +305,35 @@ built-ins — no code, and a first experience can lean on them.
239
305
 
240
306
  **Task agents** are for the one LLM subtask deterministic code cannot do —
241
307
  extraction, summarisation, classification, normalisation. Not for API calls,
242
- CRUD, routing or parsing a known shape. Created in Agent Studio, called with
243
- `runTask({ taskKey, data })` — published, or it does not resolve. `data` carries
244
- facts, never `prompt` or `instructions`. One owner per concern: extraction
245
- reads, normalisation converts, nothing does both.
308
+ CRUD, routing or parsing a known shape. Create one with `create_task_agent` and
309
+ publish it with `publish_task_agent` (with the human's yes - it changes their
310
+ account); call it with `getAgent().runTask({ taskKey, data })` - published, or
311
+ it does not resolve. `data` carries facts, never `prompt` or `instructions`. One
312
+ owner per concern: extraction reads, normalisation converts, nothing does both.
246
313
  <https://docs.foldspace.ai/user-guides/task-agents/>
247
314
 
248
- **Changing something already live is a production deploy** — immediate, no
249
- review, no rollback. Never edit it to test. Stand up a `_gen2` beside it, point
250
- the code at that, and roll back by pointing back. `_gen2`, never `_v2` —
251
- Foldspace owns the `v` counter. The suffix is permanent.
315
+ **Publishing an edit to something already live changes the live agent at
316
+ once** - unreviewed, and publishing the earlier version again puts back its
317
+ schema but not the code you changed to match the new one. Never edit it to
318
+ test. Stand up a `_gen2` beside it, point the code at that, and roll back by
319
+ pointing back. `_gen2`, never `_v2` - Foldspace owns the `v` counter. The
320
+ suffix is permanent.
252
321
 
253
322
  ### The moment a handler exists, prove it
254
323
 
255
324
  Do not batch verification to the end. As soon as a handler is registered:
256
- build, confirm it reached `dist/index.js`, attach, confirm
257
- `registration_ok`, execute it against the live endpoint, and check the result
258
- is **not empty**. An empty widget that renders cleanly is not a working
259
- experience.
260
-
261
- Say what that proved and what it did not: executing a handler proves the
262
- handler; only a real chat turn proves the experience (gate 5). Then record the
263
- endpoint, auth source, response shape and row count in `docs/app-profile.md`,
264
- with how you established each.
325
+ build, confirm it reached `dist/index.js`, `attach --stop` and attach again
326
+ with the same flags (**Local loop**), confirm `registration_ok`, and
327
+ `npx foldspace observe read` the endpoint it calls. Once the action is
328
+ published (**Ask before publishing**),
329
+ `npx foldspace ask "<the question>" --retry 2` and check that the reply - and
330
+ the card, when the action draws one - shows real data: an empty answer only
331
+ proves the call ran.
332
+
333
+ Say what that proved and what it did not: `observe read` proves the endpoint
334
+ answers for this account; only the chat turn proves the experience (gate 5).
335
+ Then record the endpoint, auth source, response shape and row count in
336
+ `docs/app-profile.md`, with how you established each.
265
337
 
266
338
  ## Reachability: everything must be published
267
339
 
@@ -271,52 +343,71 @@ with how you established each.
271
343
  | Task agent | **yes** | a `taskKey` in code names it |
272
344
  | Navigation route | **yes** | `isActive: true` |
273
345
 
274
- Publishing a **new** object is inert; publishing an **edit to a live one** is
275
- the deploy above. **Navigation has two switches, and both must be on.** Each
276
- route has its own `isActive`, set when you create it (`create_navigation_route`
277
- / `bulk_create_navigation_routes`, `isActive: true`). The agent has one
278
- agent-level switch, **off by default on a new subscription**: `publish_navigation`
279
- turns it on, `unpublish_navigation` off, and neither touches the routes' own
280
- `isActive`. `list_navigation_routes` shows both. There is no MCP tool to flip a
281
- single route's `isActive` after creation (it was disabled until Agent Studio
282
- can reverse it) - create it right, or `update_navigation_route`, which replaces
283
- the whole route. Route descriptions are a retrieval surface: write what the
284
- page is *for*, in the user's words, and pair every parameterised route with the
285
- lookup action that produces its parameter — or ship neither.
286
-
287
- **Ask before publishing.** It is required only so the copilot can call the
288
- action, and it is a live product change when the agent has real users.
346
+ **The agent offers only what the browser has.** The SDK tells Foldspace the
347
+ key of every handler loaded on the page, and the agent offers only the
348
+ published actions among those keys - none, on a page that loads no handlers.
349
+ So a published action reaches someone only when its code is in what their
350
+ browser loads: the test window has it while attach runs; their users get it
351
+ once the code is in the bundle their site loads (**Done, and what you say at
352
+ the end**). A handler whose key does not match the action's key is never
353
+ offered.
354
+
355
+ A **new** action or task agent therefore changes nothing for their users until
356
+ its code reaches them. A navigation route needs no code: once both switches
357
+ below are on, it is live for them. An **edit to anything live** changes the
358
+ live agent at once - an action or task agent when you publish the edit
359
+ (above), a navigation route as soon as `update_navigation_route` replaces it.
360
+ **Navigation has two switches, and both must be on.** Each route has its own
361
+ `isActive`, set when you create it
362
+ (`create_navigation_route` with `is_active: true`, or `isActive: true` per route
363
+ in `bulk_create_navigation_routes`). The agent has one agent-level switch, **off
364
+ by default on a new subscription**: `publish_navigation` turns it on,
365
+ `unpublish_navigation` off, and neither touches the routes' own `isActive`.
366
+ `list_navigation_routes` shows both. To change one route's switch later,
367
+ `update_navigation_route` - it replaces the whole route, so read it first
368
+ (`get_navigation_route`) and send every field back. Route descriptions are a
369
+ retrieval surface: write what the page is *for*, in the user's words, and pair
370
+ every parameterised route with the lookup action that produces its parameter -
371
+ or ship neither.
372
+
373
+ **Ask before publishing.** It changes their Foldspace account, and an edit to
374
+ something live - or a new navigation route - reaches their users at once.
289
375
 
290
376
  ## Local loop
291
377
 
292
378
  ```bash
293
- npm run dev
379
+ npm run build
294
380
  npm run inject
295
381
  npx foldspace attach --daemon
296
382
  ```
297
383
 
298
384
  `inject` launches an isolated Chrome profile and records its debug port; it
299
385
  loads nothing. In a first session `attach` comes straight after sign-in (L0),
300
- and `foldspace observe` reads the same window while it runs. `attach` prepares the page and loads the local
301
- `dist/index.js` over CDP. Coding agents use `--daemon` so the tool returns after
302
- `[lifecycle] inspect_registration:…`. An empty local registry is valid; `npm run
303
- build` is still required so `dist/index.js` exists.
386
+ and `foldspace observe` reads the same window while it runs. `attach` prepares
387
+ the page and loads the local `dist/index.js` over CDP. Use `--daemon` so the
388
+ command returns after `[lifecycle] inspect_registration:…`. An empty local
389
+ registry is valid; `npm run build` is still required so `dist/index.js` exists.
304
390
 
305
391
  Attach mode is read off the page, never asked: default swap when the page
306
392
  already has the configured product and agent; `--bootstrap` when it has no
307
393
  Foldspace SDK; `--replace` when it embeds a different product or agent, or an
308
394
  SDK without the configured agent.
309
395
 
310
- `inspect_registration:registration_ok` is the signal. On `registration_mismatch`
311
- read `npx foldspace help attach --json` and map `missingActionNames`,
312
- `unexpectedActionNames`, `diagnosticError`. A running attach keeps the actions
313
- it loaded: after a rebuild, `attach --stop` and attach again to load the new
314
- ones. That is the only reason to stop it.
315
-
316
- Prove a named action through the visible agent: after the human talks to the
317
- copilot, read the daemon log for `[actions]` SDK callback and local
318
- execute/render lines. Those record names, statuses, durations and parameter keys
319
- only — never results or error bodies.
396
+ `inspect_registration:registration_ok` is the signal. On
397
+ `registration_mismatch`, the attach log says what differs - another agent or
398
+ product on the page, or action names that do not match - and the next step:
399
+ take it. A running attach keeps what it started with - the actions, the hosts
400
+ and the flags - so after a rebuild, `attach --stop` and attach again with the
401
+ same flags; after a change to `hosts`, `npm run inject` between the two
402
+ (**One product, several hosts**). Those, a mismatch's next step, and checking
403
+ published code on dev (**Done, and what you say at the end**) are the only
404
+ reasons to stop it.
405
+
406
+ Prove a named action through the visible agent. For your own chat turn,
407
+ `npx foldspace ask` reports whether `execute` and `render` ran; after a turn
408
+ of the human's, read the attach log for the `[actions]` SDK callback and local
409
+ execute/render lines. Those record names, statuses, durations and parameter
410
+ keys only - never results or error bodies.
320
411
 
321
412
  ## Done, and what you say at the end
322
413
 
@@ -324,7 +415,44 @@ A first session is done when the customer has seen one experience answer from
324
415
  their own data, in the test window, and been invited to try it. Not when the
325
416
  gates pass, and not when there is nothing left to improve.
326
417
 
327
- If they ask *"how do I put this in front of my users?"*, the honest answer is:
418
+ **With a code project** (step 5 found or created one), putting it in front of
419
+ their users is yours to do. Once they have tried it in the test window, before
420
+ you offer the second wave:
421
+
422
+ 1. Say *"I'm saving your changes and publishing them to dev"* - no need to
423
+ ask - then save with `repo_write` and publish with the `deploy` tool, the
424
+ way `repo_read` command `instructions` says. One exception: a dev preview
425
+ that comes back with no warnings means this agent loads dev for its users -
426
+ then ask first, as for production. In chat it is "saving your changes",
427
+ "publishing to dev" and "publishing for your users" - never GitHub,
428
+ branches, commits or tool names.
429
+ 2. Check dev yourself. Say in chat that you are checking it in that window
430
+ and to leave it alone until you say it is ready: with attach stopped the
431
+ page draws no pill. Then `npx foldspace attach --stop` (while attach runs,
432
+ the page serves your local build), `npx foldspace published dev`, and
433
+ `npx foldspace ask "<the question>" --retry 2`. Without attach, `ask`
434
+ cannot see `execute` and `render`: the reply, the card text and the
435
+ screenshot are the proof - look at the screenshot. If it fails, go no
436
+ further: attach again, fix it here, then save, publish to dev and check
437
+ again. If it works, invite them to try it in that window (a reload takes
438
+ it off dev), and run `npx foldspace attach --daemon` before you build
439
+ anything else. If `published` says this window cannot show dev, say so in
440
+ one sentence, attach again, and stop at step 1.
441
+ 3. Once the dev check passed, ask before publishing for their users:
442
+ *"Shall I publish it for your users?"* On a yes, publish to production. On a not yet, it stays on dev:
443
+ say so in one line and carry on.
444
+
445
+ Steps 2 and 3 need a site that loads this agent by itself. If you attached
446
+ with `--bootstrap` or `--replace`, it does not yet: do step 1, keep attach
447
+ running, and give the answer below for the rest - their site loading the agent
448
+ is the step the Foldspace team does with them.
449
+
450
+ A save refused because someone saved after you read is not final: the
451
+ instructions say how to redo it. Any other refusal from the Foldspace tools
452
+ here is final: then the answer below.
453
+
454
+ **Without a code project**, the answer to *"how do I put this in front of my
455
+ users?"* is:
328
456
 
329
457
  > What you've seen runs in a test window on your machine. Putting it in front
330
458
  > of your users is a separate step, and the Foldspace team does it with you.
@@ -332,8 +460,9 @@ If they ask *"how do I put this in front of my users?"*, the honest answer is:
332
460
  > <support@foldspace.ai>. Everything built today carries over: the actions are
333
461
  > already in your Foldspace account, and the code is in this folder.
334
462
 
335
- Never run `foldspace deploy`, never install cloud tooling, never ask them for
336
- cloud credentials: `deploy` is Foldspace-internal and no customer can run it.
463
+ Never run `npx foldspace deploy` - that command is for Foldspace staff only,
464
+ and it is not the `deploy` tool you publish a code project with. Never install
465
+ cloud tooling, and never ask them for cloud credentials.
337
466
 
338
467
  ## Verification gates
339
468
 
@@ -347,17 +476,26 @@ Do not report success without all six:
347
476
  action the captured registry includes it.
348
477
  5. The action behaves against the real workflow: `[actions]
349
478
  local-handler:execute` in the attach log after `foldspace ask` (or the
350
- human) exercises the visible agent. A widget also needs `local-handler:render` from a real chat
351
- turn — not a hand-built `render()` call. `runAction` refuses render actions
352
- and driving `render()` yourself never runs `execute()`.
479
+ human) exercises the visible agent. A card also needs
480
+ `local-handler:render` from a real chat turn - not a hand-built `render()`
481
+ call. A view card leaves the chat free: in `ask`'s screenshot the panel's
482
+ message box does not read "Provide input, or cancel" beside a Cancel
483
+ button. If it does, `render` uses `callback` or `cancel` somewhere, or the
484
+ handler sets `awaitUserInput` - take them out. Then ask a follow-up
485
+ question about the card (`foldspace ask` again): the reply quotes its
486
+ numbers exactly as the card shows them. If it does not, make `execute`
487
+ return them as the card shows them (rule 2). An input card holds the chat
488
+ until the user answers or closes it, unless it calls `readyState`
489
+ (**Agentic UI components**).
353
490
  6. Browser evidence came from the live target, not hand-authored examples.
354
- Neighbour fixtures still pass when they exist.
355
491
 
356
492
  ## Rules that are not negotiable
357
493
 
358
494
  1. **Handlers return data only.** Never `directive`, `instructions`, or a
359
- paragraph telling the copilot what to say — those live in Agent Studio.
360
- Never return `ApiResult.detail` from `execute`; it is for the console.
495
+ paragraph telling the agent what to say - those live in Agent Studio.
496
+ Never return from `execute` a token, a cookie, any other secret or
497
+ `ApiResult.detail` (it is for the console): the agent reads what
498
+ `execute` returns for an action with no card or a view card (rule 2).
361
499
  Validate parameters and return sanitised errors.
362
500
  **What goes back to the agent states what happened - it never says what to
363
501
  do next.** A status is state: it succeeded, it failed and why (`reason`),
@@ -369,37 +507,47 @@ Do not report success without all six:
369
507
  `success: true`, the query, zero matches - *"No <records> match
370
508
  '<query>'."* - and it stops there.
371
509
  2. **`render` receives `execute`'s return value**, not the input params. A
372
- widget that lacks the ids the card needs passes in isolation and fails in the
373
- real chat. <https://docs.foldspace.ai/guides/in-chat-ui/>
510
+ card built from the params passes in isolation and fails in the real chat:
511
+ return from `execute` every id the card needs. For a view card the agent
512
+ gets that same value as soon as `render` returns and answers questions
513
+ about the card from it, so return the facts the card shows, as it shows
514
+ them, and those ids - never the raw API response. To change what the
515
+ agent gets, change `execute`, not the card (**Agentic UI components**).
516
+ <https://docs.foldspace.ai/guides/in-chat-ui/>
374
517
  3. **Say what you verified and what you assumed.** "The docs say X" and "I
375
518
  think X" are different claims — mark which. Search `docs.foldspace.ai` over
376
519
  MCP (`search_docs`, `fetch_page`) before asserting how the platform behaves;
377
520
  cite the page, or say plainly that the docs do not cover it and write down
378
521
  what you learned.
379
522
  4. **Test through the agent, then let them.** In a first session, prove an
380
- experience yourself before the customer sees it: once the action is
381
- published and registered, `npx foldspace ask "<the question>" --retry 2`
382
- types it into the agent's panel in the test window the way a person would,
383
- and reports the reply, the card's text, whether `execute` and `render` ran
384
- (from the attach log) and a screenshot. A reply with no action fired is
385
- publish lag or a wrong key - `--retry` covers the lag. Look at the
386
- screenshot. Only then the ready pill and the invitation to try it; never
387
- hand the first test to the customer. **Ask only what reads.** A question
388
- that makes the agent create, send, change or delete something is you
389
- writing in their product: ask it only in an environment the human has
390
- named as a test one, or let them type it. Those conversations appear in their
391
- Foldspace dashboard, and that is wanted: seeing their own conversations
392
- recorded is part of what they came to see. Never pass `--test-mode` in a
393
- first session.
523
+ experience yourself before the human sees it: once the action is published
524
+ and registered, `npx foldspace ask "<the question>" --retry 2` types it into
525
+ the agent's panel in the test window the way a person would, and reports the
526
+ reply, the card's text, whether `execute` and `render` ran (from the attach
527
+ log) and a screenshot. A reply with no action fired is publish lag or a wrong
528
+ key - `--retry` covers the lag. Look at the screenshot. Only then
529
+ `npx foldspace badge ready` and the invitation to try it; never hand the
530
+ first test to the human. **Ask only what reads.** A question that makes the
531
+ agent create, send, change or delete something is you writing in their
532
+ product: ask it only in an environment the human has named as a test one, or
533
+ let them type it. A card that writes only when the user approves it
534
+ (`recipes/create-a-record`) is the exception: the question draws the card,
535
+ `ask` presses nothing on it, and nothing is written - check what it shows,
536
+ and leave its buttons to the human. The conversations you start appear in
537
+ their Foldspace dashboard, and that is wanted: seeing their own
538
+ conversations recorded is part of what they came to see. Never pass
539
+ `--test-mode` in a first session.
394
540
  5. **Test mode is for an agent that already has real users.** Then `attach
395
541
  --test-mode` keeps your conversations out of their data. It is not a safety
396
542
  net: publishing still changes the live agent. Working on an agent in
397
543
  production - test mode, pinned action versions, what may be published - is
398
- a separate playbook, not this one.
544
+ a separate playbook, not this one. Publishing its code with the `deploy`
545
+ tool is the exception: it takes the same dev-then-production path as a new
546
+ agent (**Done, and what you say at the end**).
399
547
  6. **The human is not necessarily a developer.** Never hand over a terminal
400
- line, a stack trace, or a choice between flags. Name experiences by outcome
401
- — *"show my plan and credits"*, not `get_account_status`. Their two jobs:
402
- sign in, and talk to the agent.
548
+ line, a stack trace, or a choice between flags. Name experiences by
549
+ outcome - *"show my plan and credits"*, not `get_account_status`. Their
550
+ jobs: sign in, talk to the agent, and say yes or no.
403
551
  7. **Be short.** No recap of what was established, no restating the plan before
404
552
  each step. Progress is one line: what you are doing, the level reached, and
405
553
  the question if there is one.
@@ -413,7 +561,7 @@ Do not report success without all six:
413
561
  and the recipes first: it may be supported in a way you have not found.
414
562
  Say "I did not find it", never "Foldspace does not support it".
415
563
  - **Build the narrowest workaround that gives them a working experience.**
416
- A prospect is never left stuck on a Foldspace limitation.
564
+ The human is never left stuck on a Foldspace limitation.
417
565
  - **Register it, in two places.** A comment in the code where it lives -
418
566
  what it works around, how you established that, and that it goes when the
419
567
  fix ships - and a row under **Workarounds** in `docs/app-profile.md`:
@@ -426,8 +574,10 @@ Do not report success without all six:
426
574
 
427
575
  Lint catches some of these in handler code; Agent Studio copy is on you.
428
576
 
429
- - **No emoji** in copilot replies, cards or handler strings. Put that in the
430
- agent's Behavior instructions, and in each new action's Studio `instructions`.
577
+ - **No emoji** in agent replies, cards or handler strings. Put that rule in
578
+ each new action's Studio `instructions` yourself. It reaches the agent's
579
+ other replies only if the human adds it to the Behavior instructions: offer
580
+ that in the second wave (item 5).
431
581
  - **Never send the user out of the host app.** No "open in <product>" button,
432
582
  no pasted URLs — navigate with a route, same tab.
433
583
 
@@ -452,26 +602,56 @@ wins**. <https://docs.foldspace.ai/customize/configuration/>
452
602
  mount code is the other place it can live, their engineers' call. Offer it in
453
603
  the second wave.
454
604
  - **An action's `execute` never calls `setConfiguration`**: a partial call from
455
- one once collapsed the agent's panel to nothing.
605
+ one can collapse the agent's panel to nothing.
456
606
 
457
607
  ## Agentic UI components: what bites
458
608
 
459
609
  An Agentic UI component is the card an action draws in the chat (`render`).
460
610
  Each of these cost a real build.
461
611
 
462
- - **A component that asks sets `awaitUserInput: true` - and then calling
463
- `callback` is mandatory, on every path.** The agent is paused until it is
464
- called: submitting, choosing "none of these" and closing the component all
465
- end in `callback` (or `cancel()` when they abort), or the conversation hangs.
466
- A component that only shows never sets the flag - it would hold the
467
- conversation until the user clicked an acknowledgement nobody needed.
468
- - **What a component sends back is the tool response: the data and the user's
469
- choice, as fields**, with the state - chosen, declined, failed - and nothing
470
- about what to do next (**Rules**, 1).
471
- - **A view-only component's data does not reach the model.** For an action
472
- that only shows something, the model is told just that *the information was
473
- displayed to the end-user* - nothing `execute` returned, nothing the component
474
- drew. So the component carries every fact, including the bad ones.
612
+ - **A card is a view card or an input card.** `render(data, host, header,
613
+ callback, cancel, readyState)` decides which: a `render` that uses
614
+ `callback`, `cancel` or `readyState` is an input card, and the agent waits
615
+ until the card reports through one of them; one that uses none of them is
616
+ a view card.
617
+ - **A view card shows something** - an account, a list, deals at risk -
618
+ with `render(data, host, header)`: no `callback`, no `cancel`. As soon as
619
+ `render` returns, the agent gets "Shown to the user. The user has not
620
+ acted on it." plus exactly what `execute` returned, and answers questions
621
+ about the card from that. So `execute` returns the facts the card shows,
622
+ as fields, and nothing else - or the failure the card draws with
623
+ `renderFailure`: `{ success: false, error, reason }`. To change what the
624
+ agent gets, change `execute`, not the card. Nothing disables a view card:
625
+ its tabs and links keep working.
626
+ - **An input card asks the user** - pick one, upload, create, approve. It
627
+ calls `callback` or `cancel` on every path: choosing, "none of these",
628
+ closing the card, failing. The agent waits until one of them runs; a path
629
+ that never calls back leaves it waiting until the user presses stop.
630
+ `callback(value)` reaches the agent as the user's answer and disables the
631
+ card; `callback(value, false)` keeps it usable. `cancel()` reaches the
632
+ agent as cancelled, with no data - when the agent needs to know how the
633
+ card was closed, call `callback` with that state instead, as
634
+ `pick-from-a-list` does (`cancelled: true`). ⚠️ The docs say the SDK also
635
+ draws a default "Cancel" control on a card; not yet checked in the live
636
+ product.
637
+ - **When the conversation should go on while the user decides,** an input
638
+ card calls `readyState(value)` (`render`'s 6th argument) once it is
639
+ drawn: the agent gets `value` as what is shown and carries on. ⚠️ The
640
+ user's answer then arrives as a follow-up turn - seen in the SDK's
641
+ headless mode (`mode: "HEADLESS"`, the customer's own chat UI), not yet
642
+ checked in the chat panel. No recipe uses it yet.
643
+ - **Never set `awaitUserInput`** on either kind: `render` decides whether
644
+ the agent waits. An input card does not need it. The chat panel ignores
645
+ it; in headless mode `awaitUserInput: true` on a view card leaves the
646
+ agent waiting for a callback that never comes, until the user presses
647
+ stop. Lint stops the build on `awaitUserInput: true` on a view card, and
648
+ warns on any other `awaitUserInput` and on a `render` that declares
649
+ `callback`, `cancel` or `readyState` but uses none of them (the SDK then
650
+ treats it as a view card).
651
+ - **What reaches the agent is the tool response: the data and the user's
652
+ choice, as fields**, with the state (chosen, declined, cancelled, failed)
653
+ and nothing about what to do next (**Rules**, 1). A view card needs no
654
+ state of its own: the SDK tells the agent it was shown.
475
655
  - **A failed or signed-out call is never drawn as zeros.** A signed-out response
476
656
  often looks like a valid empty one: a 200 with zeros, nulls or an empty list.
477
657
  Before you draw numbers, check that the response belongs to a signed-in user -
@@ -484,11 +664,15 @@ Each of these cost a real build.
484
664
  stays clean, and the action is recorded as succeeded. Only looking at it shows
485
665
  the problem. Draw the empty or the failed state (`renderEmpty`,
486
666
  `renderFailure`) - never nothing.
487
- - **Totals and money are never computed by the model.** Compute them in the
488
- action's code or in the component, from the API's own numbers, and show them
489
- in the component. The action's instructions tell the model not to restate or
490
- recompute them: a model that adds up line items will eventually add them up
491
- wrong.
667
+ - **Totals and money are never computed by the agent.** Compute them in
668
+ code, from the API's own numbers - in `execute` for a view card, since its
669
+ result is what the agent gets - show them on the card, and hand the agent
670
+ the same figures. In the action's Studio `instructions`, tell the agent to
671
+ answer follow-up questions from the data it receives, to quote money and
672
+ counts exactly as given, never to add them up itself (a model that adds up
673
+ line items will eventually add them up wrong), and, right after a view
674
+ card, to say one short sentence at most - never repeat what the card
675
+ shows.
492
676
  - **A card matches the panel, not the page.** The card sits in the agent's
493
677
  panel - white by default (the agent's theme: `backgroundColor #FFFFFF`,
494
678
  `primaryColor` for accents; a `darkTheme` when the panel is dark, and the
@@ -499,13 +683,8 @@ Each of these cost a real build.
499
683
  imports it, and the recipes' `brand.ts` files are placeholders for it. A
500
684
  dark app keeps its accent and font; its light-on-dark text would vanish on
501
685
  white, so that falls back to greys and the file says so. Record where the
502
- values came from in the profile's Brand section. Never copy colours by eye
503
- (seen 2026-09-22: a dark card in a white panel).
504
- - **Plain hex or rgba only.** The panel saves every card to the conversation
505
- history as an image drawn with html2canvas, which cannot read `oklch()`,
506
- `lab()` or `color-mix()`: one such value and the saved card is blank
507
- (`data:,` where the card was). Lint flags them. Do not read the app's CSS
508
- variables either - on modern apps they resolve to oklch.
686
+ values came from in the profile's Brand section. Never copy colours by eye:
687
+ that is how a dark card ends up in a white panel.
509
688
 
510
689
  ## Layout
511
690
 
@@ -527,20 +706,26 @@ Do not introduce another bundler or bundle format.
527
706
  ## Working examples to copy from
528
707
 
529
708
  Before writing a handler or a card, read
530
- `node_modules/@foldspace_npm/harness/recipes/INDEX.md` — the whole table, it is
531
- short — and open the recipe closest to the outcome. Each is a finished
532
- piece of a build, generalised from production builds with the customer taken
533
- out: the API helper, the handler or card, and a recorded response per state. Copy its `agent/` files across, then replace the
534
- placeholder path and fields with what you observed. They match the harness
535
- version you have installed, and every one is built and run on each release.
709
+ `node_modules/@foldspace_npm/harness/recipes/INDEX.md` - the whole table, it is
710
+ short - and open the recipe closest to the outcome. Each is a finished piece of
711
+ a build, generalised from production builds with the customer taken out: its
712
+ code and its fixtures (for most card recipes, a recorded response per state).
713
+ Copy its `agent/` files across, then replace the placeholder path and fields
714
+ with what you observed. They match the harness version you have installed, and
715
+ every one is built and run on each release.
536
716
 
537
717
  After the first experience → `who-is-the-user`. L2 → `find-by-name`, and
538
718
  `pick-from-a-list` when the user has to choose. The user's records on one
539
719
  card → `records-list`. The product lives on several hosts → `several-hosts`.
540
- Show one account on a card → `account-overview`. Upload a
720
+ The data arrives inside the HTML, with no JSON call → `server-rendered-forms`.
721
+ Show one account the user names → `lookup-then-view` (start here). The app
722
+ already hands over the id → `account-overview`. Upload a
541
723
  CSV of contacts → `upload-contacts`. Save a note on a meeting →
542
- `update-meeting-notes`. Prepare for a meeting → `prepare-for-a-meeting`. The records that need
543
- attention, with reasons → `opportunities-at-risk`.
724
+ `update-meeting-notes`. Create one record from what the user said, edited on a
725
+ card before it is written → `create-a-record` (all at once, or one question per
726
+ card then a review - its README says which). Prepare for a meeting →
727
+ `prepare-for-a-meeting`. The records that need attention, with reasons →
728
+ `opportunities-at-risk`.
544
729
  App does not use a bearer token → `swap-the-login-method`.
545
730
  Each says how many production builds carry the pattern and what they learned.
546
731
  If nothing fits — there is no L1 card recipe yet, and no navigation recipe —
@@ -557,11 +742,11 @@ not import it — fix the import when you implement.
557
742
  | `publicFetch` | Unauthenticated / marketing origin | Signed-in product APIs |
558
743
  | `getAuthToken` / `parseJwt` | `AUTH_SOURCE` is `localStorage` or `cookie` | Custom header schemes — override `apiFetch` in `utils.ts` |
559
744
  | `rankBy` | User typed a name; API search is exact or ignored | Domain ranking (invoices, coverage, industry classification codes) |
560
- | `renderLoading` / `Empty` / `Error` / `Fatal` | Chatterblock states | Branded cards — those stay in `agent/views/` |
745
+ | `renderLoading` / `Empty` / `Error` / `Fatal` | Plain states inside a card (loading, empty, error) | Branded cards - those stay in `agent/views/` |
561
746
  | `renderFailure` | Any `{ ok: false }` from `apiFetch` — it picks the state from `reason` | You need bespoke copy for one reason; then branch on `reason` yourself |
562
747
  | `httpFailure` / `networkFailure` | Your own `apiFetch` (custom header, cookies) — keeps the same `reason`s | The default `apiFetch` fits; then you never call these |
563
- | `getAgent` | Talking to Foldspace | You need a specific instance — then `agentIds()` |
564
- | `armAllInstances` | Attach/bootstrap setup | Inside `execute` |
748
+ | `getAgent` | Talking to Foldspace (the overlay instance) | You need a specific instance - then `window.foldspace.agent({ apiName, mode })`, with `apiName` the Agent Key and `mode` in capitals (`"EMBEDDED"`). `window.foldspace.agentIds()` lists the instances in lower case (`"embedded-<apiName>"`): ask only for one it lists - `agent()` creates an instance that does not exist |
749
+ | `armAllInstances` | Never in agent code - `attach` handles the page, and test mode is `attach --test-mode`, only for an agent that already has real users | Anywhere in `agent/`: it ships to production |
565
750
  | `redact` / `mapWithConcurrency` | Logging tokens; batching fetches | — |
566
751
 
567
752
  Every failed call carries a `reason` — `signed_out`, `forbidden`, `not_found`,
@@ -571,13 +756,20 @@ building is not the session at run time: **expect `signed_out`** and let
571
756
  `renderFailure` show it. `not_found` and zero rows are both `renderEmpty` with
572
757
  the query named — never an error, never a blank card.
573
758
 
574
- If the observed auth is not Bearer + `localStorage`/`cookie`, stop re-exporting
575
- that one function and keep the rest:
576
-
577
- ```ts
578
- export { getAgent, rankBy, renderEmpty } from "@foldspace_npm/harness/runtime";
579
- export { apiFetch } from "./api";
580
- ```
759
+ If the observed auth is not Bearer + `localStorage`/`cookie`, copy
760
+ `node_modules/@foldspace_npm/harness/recipes/swap-the-login-method/agent/utils.ts`
761
+ over `agent/utils.ts` - or, when the browser's own cookies carry the sign-in and
762
+ there is no token, that recipe's `variants/utils.cookies.ts` - and set what its
763
+ README's **Adapt it** table lists to what you observed.
764
+
765
+ **If `agent/` came from a code project** (step 5), nothing in it may import a
766
+ package: Foldspace builds the published bundle with no `node_modules`, so a
767
+ package import passes every gate in this manual and then fails on publish.
768
+ Import the helpers the way the project's own `agent/utils.ts` does - from
769
+ `./runtime` - in a recipe file you copy too. A project with an `agent/utils/`
770
+ folder has its own helpers instead: change the sign-in there, and never add an
771
+ `agent/utils.ts` beside it, which would win the import. Without a code project,
772
+ the package import is right.
581
773
 
582
774
  ## Safety
583
775
 
@@ -586,10 +778,13 @@ export { apiFetch } from "./api";
586
778
  what you wanted it for, or take a narrower read-only route. Never retry it,
587
779
  and never try a different command hoping it gets through - not re-quoted, not
588
780
  in pieces, not through a helper script or a subagent: a denial covers the
589
- outcome, not the string. If a `foldspace` command itself was denied, name it,
590
- give the one-line allow rule (`Bash(npx foldspace:*)` in
591
- `.claude/settings.local.json`), and hand that step to the human - for a chat
592
- turn, that is them typing the question into the panel.
781
+ outcome, not the string. If the session's own checks - not the human -
782
+ denied a `foldspace` command other than `init`, `upgrade` or `deploy`, name
783
+ it and give the human the allow rule for that one command only, to add to
784
+ `.claude/settings.local.json` - for `observe`,
785
+ `Bash(npx foldspace observe:*)` - and meanwhile hand that step to them: for
786
+ a chat turn, that is them typing the question into the panel. `init`,
787
+ `upgrade` and `deploy` ask on purpose: never give an allow rule for them.
593
788
  - **Never create an account or a key** - not in Foldspace, not in the
594
789
  customer's product, not anywhere. If one is missing, say which and where it
595
790
  goes. **Never widen your own access**: if something is refused for a site or
@@ -597,5 +792,8 @@ export { apiFetch } from "./api";
597
792
  - Do not commit secrets, cookies, HAR files, browser storage, or
598
793
  `.foldspace-dev/`. Nothing transient goes in `docs/`.
599
794
  - Local handler changes are not cloud publication.
600
- - Creating or publishing Foldspace resources requires explicit approval.
795
+ - Creating or publishing Foldspace resources needs the human's yes - except
796
+ the agent's code: creating its code project, saving to it and publishing it
797
+ to dev, unless the dev preview comes back with no warnings (this agent then
798
+ loads dev for its users). Say you are doing it, then do it.
601
799
  - Action keys and parameter names must match the published schema.