howone 0.1.31 → 0.1.32

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 (19) hide show
  1. package/package.json +1 -1
  2. package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +33 -27
  3. package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +2 -2
  4. package/templates/vite/.howone/skills/howone/02-entity-schema/01-schema-design.md +9 -29
  5. package/templates/vite/.howone/skills/howone/02-entity-schema/02-schema-operations.md +166 -235
  6. package/templates/vite/.howone/skills/howone/02-entity-schema/03-access-models.md +151 -0
  7. package/templates/vite/.howone/skills/howone/02-entity-schema/04-query-contracts.md +123 -0
  8. package/templates/vite/.howone/skills/howone/02-entity-schema/05-ai-persistence-patterns.md +84 -201
  9. package/templates/vite/.howone/skills/howone/03-ai-capabilities/01-ai-capability-architecture.md +37 -32
  10. package/templates/vite/.howone/skills/howone/03-ai-capabilities/02-workflow-contract-rules.md +5 -4
  11. package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +89 -204
  12. package/templates/vite/.howone/skills/howone/03-ai-capabilities/05-ai-feature-playbooks.md +8 -29
  13. package/templates/vite/.howone/skills/howone/04-app-sdk/01-client-setup.md +1 -2
  14. package/templates/vite/.howone/skills/howone/04-app-sdk/07-ai-action-calls.md +2 -2
  15. package/templates/vite/.howone/skills/howone/04-app-sdk/08-ai-manifest-handoff.md +6 -5
  16. package/templates/vite/.howone/skills/howone/04-app-sdk/09-extension-boundaries.md +1 -1
  17. package/templates/vite/.howone/skills/howone/{02-entity-schema/03-data-access-patterns.md → 04-app-sdk/11-entity-data-access-patterns.md} +4 -4
  18. package/templates/vite/.howone/skills/howone/{02-entity-schema/04-query-dsl-and-responses.md → 04-app-sdk/12-query-dsl-and-responses.md} +1 -1
  19. package/templates/vite/.howone/skills/howone/SKILL.md +112 -75
@@ -0,0 +1,123 @@
1
+ # Backend Query Contracts
2
+
3
+ Use this reference while designing entity query behavior: filters, sorts, pagination, indexes, and
4
+ public constraints. It is backend-only; SDK query syntax belongs in `04-app-sdk/12-query-dsl-and-responses.md`.
5
+
6
+ ## Query Surface
7
+
8
+ Every list/detail experience should be traceable to fields declared in the entity schema:
9
+
10
+ | Need | Contract field |
11
+ |---|---|
12
+ | Filter by status/category/slug | `access.public.allowedFilters` for public reads; schema field exists |
13
+ | Sort by date/rank/title | `access.public.allowedSorts` for public reads; `performance.allowedSorts` for app reads |
14
+ | Fast owner history | owner-scoped index such as `["status", "updatedDate"]` |
15
+ | Public one-record page | `public.read = "scoped"`, `requiredScopes`, low `maxLimit` |
16
+ | Search | explicit product support; do not use as a replacement for scopes |
17
+
18
+ ## Filter Fields
19
+
20
+ Only expose fields that are safe and stable.
21
+
22
+ Good public filters:
23
+
24
+ ```json
25
+ {
26
+ "allowedFilters": ["slug", "status", "category", "active"]
27
+ }
28
+ ```
29
+
30
+ Avoid public filters for:
31
+
32
+ - private prompt text;
33
+ - owner/user identifiers unless they are intentional route scopes;
34
+ - internal moderation fields;
35
+ - payment, token, or provider metadata;
36
+ - high-cardinality fields that have no index and will be queried frequently.
37
+
38
+ ## Sort Fields
39
+
40
+ Prefer predictable date or rank fields:
41
+
42
+ ```json
43
+ {
44
+ "allowedSorts": ["publishedAt", "updatedDate"],
45
+ "defaultLimit": 20,
46
+ "maxLimit": 100
47
+ }
48
+ ```
49
+
50
+ Rules:
51
+
52
+ - Public sort fields must be explicitly listed.
53
+ - Sort fields used by large lists should have supporting indexes.
54
+ - Do not expose arbitrary revenue, score, or internal ranking fields publicly unless the product
55
+ explicitly needs them.
56
+ - Keep public `maxLimit` bounded.
57
+
58
+ ## Index Planning
59
+
60
+ Design indexes from actual access patterns:
61
+
62
+ ```json
63
+ {
64
+ "indexes": [
65
+ { "name": "owner_updated", "fields": ["updatedDate"], "scope": "owner" },
66
+ { "name": "owner_status_updated", "fields": ["status", "updatedDate"], "scope": "owner" },
67
+ { "name": "public_slug_unique", "fields": ["slug"], "unique": true },
68
+ { "name": "public_status_published", "fields": ["status", "publishedAt"], "scope": "global" }
69
+ ]
70
+ }
71
+ ```
72
+
73
+ Use owner-scoped indexes for private per-user histories. Use global indexes for anonymous public
74
+ lists. Use unique indexes for slug/share IDs when routes depend on uniqueness.
75
+
76
+ ## System Fields
77
+
78
+ Generated records usually expose system fields such as:
79
+
80
+ | Concept | Typical field |
81
+ |---|---|
82
+ | record id | `id` |
83
+ | created date | `createdDate` |
84
+ | updated date | `updatedDate` |
85
+ | owner | `createdById` |
86
+ | schema version id | `schemaVersionId` |
87
+ | schema version number | `schemaVersionNumber` |
88
+
89
+ Do not design app behavior around raw storage-only names unless the manifest or SDK reference
90
+ requires that shape.
91
+
92
+ ## Pagination Defaults
93
+
94
+ Every list contract should define practical limits:
95
+
96
+ ```json
97
+ {
98
+ "performance": {
99
+ "defaultLimit": 20,
100
+ "maxLimit": 100,
101
+ "allowedSorts": ["updatedDate", "createdDate"]
102
+ }
103
+ }
104
+ ```
105
+
106
+ Public scoped detail pages should usually use:
107
+
108
+ ```json
109
+ {
110
+ "defaultLimit": 1,
111
+ "maxLimit": 1
112
+ }
113
+ ```
114
+
115
+ ## Query Contract Checklist
116
+
117
+ - Every planned filter field exists in `properties`.
118
+ - Public filters and sorts are explicitly allowlisted.
119
+ - Owner/private histories have owner-scoped indexes.
120
+ - Public lists have global indexes for common filter/sort combinations.
121
+ - Scoped public pages include required scope fields and low limits.
122
+ - List pages have pagination limits before SDK/UI code starts.
123
+ - Query implementation is written only after `.howone/database/manifest.json` is synced.
@@ -1,77 +1,48 @@
1
- # AI Persistence Patterns
1
+ # AI Persistence Entity Patterns
2
2
 
3
- Use this reference when an AI workflow output must become durable product data: generation history,
4
- saved results, analysis reports, retryable jobs, share pages, or user libraries.
3
+ Use this reference after the AI output schema is known and the product needs durable data:
4
+ generation history, saved results, reports, retryable jobs, share pages, or user libraries.
5
5
 
6
- AI workflow contracts describe **how to produce an output**. Entity schemas describe **what the app
7
- persists, lists, edits, shares, and reloads**. Do not merge those two responsibilities.
6
+ This is a backend entity design reference. It does not define SDK calls. App-side execution and
7
+ persistence code belongs in `04-app-sdk/07-ai-action-calls.md` and
8
+ `04-app-sdk/08-ai-manifest-handoff.md`.
8
9
 
9
- ## Core Rule
10
+ ## Core Boundary
10
11
 
11
12
  ```text
12
13
  AI capability outputSchema != database entity schema
13
14
  ```
14
15
 
15
- The output schema is the workflow return contract. It can contain transient execution details,
16
- intermediate data, model metadata, or provider-shaped structures. The database schema is a product
17
- contract. It should store only fields that the app needs after refresh, across sessions, or on public
18
- pages.
16
+ The AI output schema is the workflow return contract. The database entity schema is the product
17
+ record contract. Persist only fields the product needs after refresh, across sessions, in lists, or
18
+ on public pages.
19
19
 
20
- For every AI feature, ask:
20
+ For every AI output field, decide:
21
21
 
22
- | Question | Persist? | Where |
22
+ | Question | Persist? | Entity design |
23
23
  |---|---:|---|
24
- | Must the user see this after refresh? | yes | Entity field |
25
- | Must it appear in history/library/search? | yes | Entity field + index if queried |
26
- | Is it only needed while streaming/running? | no | Local UI state / runtime event |
27
- | Is it provider debug data? | usually no | Logs, not entity data |
28
- | Is it needed to retry or resume? | yes | Entity field |
29
- | Is it sensitive model/provider metadata? | usually no | Avoid public entity fields |
30
-
31
- ## Recommended Flow
32
-
33
- For long-running or user-visible AI operations, create a pending record before calling the workflow.
34
-
35
- ```ts
36
- import { runAiActionAndPersist } from '@howone/sdk'
37
-
38
- await runAiActionAndPersist({
39
- entity: howone.entities.Generation,
40
- input: { prompt },
41
- createPending: (input) => ({
42
- prompt: input.prompt,
43
- status: 'pending',
44
- requestedAt: new Date().toISOString(),
45
- }),
46
- run: (input) => howone.ai.generateImage.run(input),
47
- mapCompleted: ({ output }) => ({
48
- status: 'completed',
49
- resultUrl: output.imageUrl,
50
- completedAt: new Date().toISOString(),
51
- }),
52
- mapFailed: ({ error }) => ({
53
- status: 'failed',
54
- errorMessage: error instanceof Error ? error.message : 'Generation failed',
55
- }),
56
- })
57
- ```
58
-
59
- Why pending-first:
24
+ | User must see it after refresh | yes | explicit field |
25
+ | It appears in history/library/search | yes | field + index if queried |
26
+ | It is needed for retry/resume | yes | input/options/status fields |
27
+ | It is only a streaming/intermediate chunk | no | runtime/UI state |
28
+ | It is provider debug metadata | usually no | logs, not product records |
29
+ | It is sensitive internal/provider data | usually no | omit or keep private-only |
60
30
 
61
- - refresh can show an in-progress item instead of losing the request;
62
- - failure can be displayed in history;
63
- - retry can reuse the original prompt/options;
64
- - support/debug can identify which input produced the failed state;
65
- - UI can render from persisted data instead of assuming local state survived.
31
+ ## Pending-First Record Model
66
32
 
67
- For very fast, disposable AI actions, persistence may be unnecessary. Do not create an entity just
68
- because an AI workflow exists.
33
+ For long-running or user-visible AI jobs, design an entity that can represent pending, completed,
34
+ and failed states. The app may create a pending record before workflow execution, then update it
35
+ after completion or failure.
69
36
 
70
- ## Status Fields
37
+ Required contract pieces:
71
38
 
72
- Every persisted AI job/history entity should have a status field.
39
+ - original user input or durable input reference;
40
+ - `status`;
41
+ - output fields for completed display;
42
+ - failure fields for history/retry;
43
+ - timestamps for list ordering and stale-job handling.
73
44
 
74
- Recommended:
45
+ Status field:
75
46
 
76
47
  ```json
77
48
  {
@@ -83,31 +54,21 @@ Recommended:
83
54
  }
84
55
  ```
85
56
 
86
- Use stable string values:
87
-
88
- | Status | Meaning |
89
- |---|---|
90
- | `pending` | Record exists, workflow has not produced final output. |
91
- | `running` | Optional if the app receives a running state after submission. |
92
- | `completed` | Output fields are valid for display. |
93
- | `failed` | `errorMessage` or failure fields explain the failure. |
94
- | `canceled` | User/system canceled and no final output should be expected. |
95
-
96
57
  Rules:
97
58
 
98
- - Do not infer completion only from `resultUrl` or another output field.
59
+ - Do not infer completion only from `resultUrl`, `summary`, or another output field.
99
60
  - Keep failed records when the product has history or retry UX.
100
- - If the UI shows a spinner from persisted data, it must also handle stale `pending/running` records.
61
+ - If pending/running records are shown from persisted data, define how stale records are recovered.
62
+ - Do not store raw event streams unless the entity explicitly needs them.
101
63
 
102
- ## Minimal Generation History Schema
64
+ ## Minimal Generation History Entity
103
65
 
104
- Use for image/text/report/music/video generation history.
66
+ Use for image, text, report, music, video, or similar generation history.
105
67
 
106
68
  ```json
107
69
  {
108
70
  "name": "Generation",
109
71
  "type": "object",
110
- "visibility": "private",
111
72
  "properties": {
112
73
  "prompt": { "type": "string" },
113
74
  "status": { "type": "string", "default": "pending" },
@@ -138,18 +99,17 @@ Use for image/text/report/music/video generation history.
138
99
  }
139
100
  ```
140
101
 
141
- Use separate result fields instead of one opaque `result` object when the UI lists or filters them.
142
- Use an object field only for genuinely nested, product-level structured results.
102
+ Prefer separate product fields over one opaque `result` object when the UI lists, filters, previews,
103
+ or shares the output. Use an object field only for genuinely nested product-level structured data.
143
104
 
144
- ## Analysis Report Schema
105
+ ## Structured Analysis Report Entity
145
106
 
146
- Use when AI returns a structured report that users browse later.
107
+ Use when AI returns a report users browse later.
147
108
 
148
109
  ```json
149
110
  {
150
111
  "name": "AnalysisReport",
151
112
  "type": "object",
152
- "visibility": "private",
153
113
  "properties": {
154
114
  "sourceTitle": { "type": "string" },
155
115
  "sourceUrl": { "type": ["string", "null"], "default": null },
@@ -165,50 +125,42 @@ Use when AI returns a structured report that users browse later.
165
125
  "access": {
166
126
  "authenticated": { "read": "own", "create": "own", "update": "own", "delete": "own" },
167
127
  "public": { "read": "none", "create": "none", "update": "none", "delete": "none" }
168
- }
128
+ },
129
+ "indexes": [
130
+ { "name": "owner_updated", "fields": ["updatedDate"], "scope": "owner" },
131
+ { "name": "owner_score_updated", "fields": ["score", "updatedDate"], "scope": "owner" }
132
+ ]
169
133
  }
170
134
  ```
171
135
 
172
136
  Mapping rule:
173
137
 
174
- ```ts
175
- const output = await howone.ai.analyzeDocument.run(input)
176
-
177
- await howone.entities.AnalysisReport.update(report.id, {
178
- status: 'completed',
179
- summary: output.summary,
180
- insights: output.insights,
181
- score: output.score,
182
- completedAt: new Date().toISOString(),
183
- })
138
+ ```text
139
+ workflow input.sourceTitle -> AnalysisReport.sourceTitle
140
+ workflow input.sourceUrl -> AnalysisReport.sourceUrl
141
+ workflow output.summary -> AnalysisReport.summary
142
+ workflow output.insights -> AnalysisReport.insights
143
+ workflow output.score -> AnalysisReport.score
144
+ runtime failure -> AnalysisReport.errorMessage
145
+ runtime completed -> AnalysisReport.completedAt
184
146
  ```
185
147
 
186
- Do not save the whole workflow envelope unless the entity has an explicitly designed field for that
187
- envelope and the product needs it.
188
-
189
- ## Public AI Result Share Page
190
-
191
- Use when a user can share one generated result publicly.
192
-
193
- Recommended split:
148
+ Do not save the whole workflow envelope unless the product explicitly needs a private object field
149
+ for audit/debug.
194
150
 
195
- - private `Generation` entity for user history and edits;
196
- - public or scoped `SharedGeneration` entity for anonymous viewing.
151
+ ## Public Share Split
197
152
 
198
- Why split:
153
+ For public AI result pages, split private history from public share data:
199
154
 
200
- - private history may include prompts, failures, drafts, and internal metadata;
201
- - public page should expose only curated fields;
202
- - public access rules stay simple and auditable;
203
- - unsharing can delete or deactivate the shared record without destroying private history.
155
+ - private `Generation` stores prompts, failures, drafts, internal metadata, and user history;
156
+ - public/scoped `SharedGeneration` stores only curated fields for anonymous viewing.
204
157
 
205
- Public scoped schema:
158
+ Scoped public share entity:
206
159
 
207
160
  ```json
208
161
  {
209
162
  "name": "SharedGeneration",
210
163
  "type": "object",
211
- "visibility": "public",
212
164
  "properties": {
213
165
  "shareId": {
214
166
  "type": "string",
@@ -241,93 +193,33 @@ Public scoped schema:
241
193
  }
242
194
  ```
243
195
 
244
- Public page:
245
-
246
- ```ts
247
- const result = await howone.public.entities.SharedGeneration.queryScoped({
248
- where: { shareId, active: true },
249
- page: { number: 1, size: 1 },
250
- })
251
-
252
- const shared = result.items[0] ?? null
253
- ```
254
-
255
- Rules:
256
-
257
- - Do not expose private prompt fields unless the product explicitly wants that.
258
- - Use `active` or a deletion flow for unshare.
259
- - Keep public `maxLimit` at `1` for one-share pages.
260
-
261
- ## Retry Design
196
+ Never make the main private generation history public just to support share pages.
262
197
 
263
- If the product has retry, store enough input fields to rebuild the workflow request.
198
+ ## Retry And Resume Fields
264
199
 
265
- Persist:
200
+ If retry is part of the product, persist enough input fields to rebuild the workflow request:
266
201
 
267
- - user prompt or source content reference;
268
- - selected mode/model/style options that affect output;
269
- - uploaded file IDs/URLs needed by the workflow;
270
- - status and failure message;
271
- - timestamps.
202
+ - prompt/source content reference;
203
+ - selected style/mode/options;
204
+ - uploaded file URLs or file IDs;
205
+ - status and error message;
206
+ - requested/completed timestamps.
272
207
 
273
208
  Do not persist:
274
209
 
275
- - temporary UI component state;
276
210
  - auth/session/token values;
211
+ - raw uploaded browser bytes;
212
+ - temporary component state;
277
213
  - raw streaming chunks;
278
214
  - provider secrets;
279
- - hidden prompt text that should stay server-side;
280
- - large binary content when file upload should store a URL or file id.
281
-
282
- Retry example:
283
-
284
- ```ts
285
- const old = await howone.entities.Generation.getOrThrow(id)
286
-
287
- const retry = await howone.entities.Generation.create({
288
- prompt: old.prompt,
289
- status: 'pending',
290
- requestedAt: new Date().toISOString(),
291
- })
292
-
293
- const output = await howone.ai.generateImage.run({
294
- prompt: old.prompt,
295
- })
296
-
297
- await howone.entities.Generation.update(retry.id, {
298
- status: 'completed',
299
- resultUrl: output.imageUrl,
300
- completedAt: new Date().toISOString(),
301
- })
302
- ```
303
-
304
- Prefer creating a new retry record when the product is history-oriented. Prefer updating the same
305
- record only when the product treats retry as replacing the original attempt.
215
+ - hidden system prompts or internal prompts.
306
216
 
307
- ## Resume Design
308
-
309
- On page load, query persisted records instead of relying on local state:
310
-
311
- ```ts
312
- const history = await howone.entities.Generation.query.mine({
313
- where: { status: { in: ['pending', 'running', 'completed', 'failed'] } },
314
- orderBy: { updatedDate: 'desc' },
315
- page: { number: 1, size: 20 },
316
- })
317
- ```
318
-
319
- Then:
320
-
321
- - render `completed` from output fields;
322
- - render `failed` from `errorMessage`;
323
- - render `pending/running` as in progress only if the app has a way to poll status;
324
- - mark stale pending records as failed/canceled when product rules define a timeout.
325
-
326
- Do not leave indefinite pending rows with no recovery path.
217
+ Prefer a new retry record for history-oriented products. Prefer updating the same record only when
218
+ retry semantically replaces the original attempt.
327
219
 
328
220
  ## Field Mapping Checklist
329
221
 
330
- Before implementing persistence, write the mapping explicitly:
222
+ Before app implementation, write the mapping explicitly:
331
223
 
332
224
  ```text
333
225
  workflow input.prompt -> Generation.prompt
@@ -339,34 +231,25 @@ runtime request started -> Generation.requestedAt
339
231
  runtime request completed -> Generation.completedAt
340
232
  ```
341
233
 
342
- If a workflow output field has no mapping, decide whether it is intentionally transient or whether
343
- the entity schema is missing a product field.
234
+ If a workflow output has no mapping, decide whether it is intentionally transient or the entity
235
+ schema is missing a product field.
344
236
 
345
237
  ## Access Checklist
346
238
 
347
- Choose access based on product behavior:
348
-
349
239
  | Product behavior | Entity access |
350
240
  |---|---|
351
241
  | User-only private generation history | authenticated own, public none |
352
- | Team/shared authenticated library | authenticated all or future role-scoped model |
242
+ | Shared authenticated library | authenticated all, public none |
353
243
  | Anonymous public gallery | public list with safe fields only |
354
244
  | One public share link | public scoped with share id |
355
- | Public submission to AI queue | public create only if abuse constraints are handled |
245
+ | Public submission to AI queue | public create only with anti-abuse constraints |
356
246
 
357
- Never make the main generation history public just to support a public share page. Create a scoped
358
- share entity or a curated public entity.
247
+ ## Persistence Checklist
359
248
 
360
- ## Common Mistakes
361
-
362
- | Mistake | Fix |
363
- |---|---|
364
- | Treating `outputSchema` as the entity schema | Design product persistence separately. |
365
- | Saving raw workflow envelopes | Map only fields the product needs after refresh. |
366
- | No status field | Add `status` and explicit failure fields. |
367
- | Creating history only after success | Create pending first when history/resume matters. |
368
- | Losing failures | Persist `failed` state and `errorMessage`. |
369
- | Publicly exposing private prompt/history | Use a separate public scoped/share entity. |
370
- | Retrying without stored inputs | Persist the inputs/options needed for retry. |
371
- | Rendering from local state only | Reload from entity queries on page load. |
372
- | Leaving stale pending forever | Add timeout/recovery behavior in product logic. |
249
+ - AI output schema is fixed before persistence schema design.
250
+ - Entity stores product fields, not workflow internals.
251
+ - Status and failure fields exist for long-running jobs.
252
+ - Retry/resume input fields are durable.
253
+ - Public share data is split from private history.
254
+ - Indexes match history/share/list query patterns.
255
+ - SDK implementation begins only after database and AI manifests are synced.
@@ -16,9 +16,9 @@ HowOne AI has five distinct layers:
16
16
  |---|---|---|
17
17
  | Product feature | User-facing goal, UX states, persistence decision | workflow internals |
18
18
  | AI capability contract | `name`, `description`, `inputSchema`, `outputSchema`, `outputEntityName`, versions, manifest | database CRUD, UI, auth |
19
- | External workflow implementation | generated/edited workflow graph behind a `workflowId` | app schema, frontend state |
20
- | Status/background layer | `request_id` polling, completed/failed state, `workflowConfigID` capture | SDK binding source |
21
- | SDK binding/app code | `defineAiAction`, Zod schemas, `howone.ai.*`, persistence through entities | workflow generation |
19
+ | External workflow implementation | generated/edited workflow graph behind a manifest `workflowId`/EAX `config_id` | app schema, frontend state |
20
+ | Status/background layer | job/task polling, completed/failed state, submitted config mapping | SDK binding source |
21
+ | SDK handoff | synced manifest action names, workflow IDs, input/output schemas | workflow generation |
22
22
 
23
23
  Do not collapse these layers. The common mistakes are:
24
24
 
@@ -34,10 +34,9 @@ Do not collapse these layers. The common mistakes are:
34
34
  user request = intent
35
35
  agent AI contract proposal = draft
36
36
  applied AI capability version = validated contract
37
- .howone/ai/manifest.json = local synced source for SDK codegen
38
- workflow service completed status = source for workflowConfigID
39
- src/lib/sdk.ts = generated app binding
40
- frontend UI = SDK consumer
37
+ .howone/ai/manifest.json = local synced source for workflow submit and later SDK codegen
38
+ external-ai-capability result = job/task/config mapping and possible manifest workflowId update
39
+ SDK/UI implementation = separate app-sdk track after AI design is complete
41
40
  entity schema = persistence contract, separate from AI contract
42
41
  ```
43
42
 
@@ -51,15 +50,14 @@ Use this flow for new AI features:
51
50
  2. Decide whether the feature is supported. If not supported, stop and explain the missing capability.
52
51
  3. Decide one workflow per user-facing feature. Use two workflows only for RAG.
53
52
  4. Design `inputSchema` and `outputSchema` using `02-workflow-contract-rules.md`.
54
- 5. Preview/apply the AI capability patch through the capability tool.
53
+ 5. Apply the AI capability patch through the capability tool.
55
54
  6. Sync `.howone/ai/manifest.json`.
56
- 7. Submit workflow create through `external-ai-capability` / workflow operate from the synced manifest.
57
- 8. Store returned `request_id` values for polling.
55
+ 7. Submit workflow create/update through `external-ai-capability` from the synced manifest.
56
+ 8. Store returned job/task IDs and submitted config IDs for polling/debugging.
58
57
  9. Poll status until `completed` or `failed`.
59
- 10. On completed + `payload.success === true`, store `payload.workflow_details.new_workflow_config_id`.
60
- 11. Generate/update `src/lib/sdk.ts` using `04-app-sdk/08-ai-manifest-handoff.md` and `01-architect/02-manifest-codegen.md`.
61
- 12. Implement UI calls through `howone.ai.<action>.run()`, `.stream()`, or `.events()`.
62
- 13. If output must persist, design entity schema and use `runAiActionAndPersist()` when appropriate.
58
+ 10. Re-read `.howone/ai/manifest.json`; update operations may have written fresh `workflowId` values.
59
+ 11. Leave AI design. If app code must call the workflow, read the SDK track and generate bindings.
60
+ 12. If output must persist, design entity schema after the output contract is fixed.
63
61
 
64
62
  Do not submit external workflow create/update from a hand-written schema. It should come from the
65
63
  synced manifest.
@@ -71,7 +69,7 @@ synced manifest.
71
69
  | New AI feature, no manifest entry | create AI capability, sync manifest, submit workflow create |
72
70
  | Manifest entry exists but no workflow created yet | submit workflow create from manifest |
73
71
  | User asks to change input/output contract | update capability contract first, sync, then submit workflow update |
74
- | User asks to improve behavior only | submit workflow update with `workflowConfigID` and `updatePrompt` |
72
+ | User asks to improve behavior only | submit workflow update with `updates[]` and `updatePrompt` |
75
73
  | User asks to save outputs/history | design/update database entity after AI output contract is known |
76
74
  | User asks for public share of AI result | private history entity + public scoped share entity |
77
75
 
@@ -81,23 +79,30 @@ Create external workflow when:
81
79
 
82
80
  - capability has a `workflowId`;
83
81
  - no confirmed external implementation exists;
84
- - no `workflowConfigID` has been captured.
82
+ - the manifest `workflowId` has not already been submitted as an implementation config.
85
83
 
86
84
  Update external workflow when:
87
85
 
88
86
  - an external implementation exists;
89
- - the status layer previously returned `payload.workflow_details.new_workflow_config_id`;
90
87
  - you have a concrete `updatePrompt`.
88
+ - `external-ai-capability` can read the current manifest `workflowId`.
91
89
 
92
- `workflowConfigID` is not the same as `workflowId`.
90
+ Current `external-ai-capability` semantics:
93
91
 
94
92
  ```text
95
- workflowId = stable workflow UUID from manifest, used by SDK execution
96
- workflowConfigID = implementation config ID from completed workflow generation/edit status
97
- request_id = async operation ID returned by workflow operate endpoint
93
+ create:
94
+ config_id = manifest capability.workflowId
95
+ mode = create
96
+
97
+ update:
98
+ previous config = current manifest capability.workflowId
99
+ new config = freshly generated UUID
100
+ manifest = rewritten so capability.workflowId is the fresh UUID
98
101
  ```
99
102
 
100
- Do not invent any of these IDs.
103
+ The SDK execution binding uses the manifest `workflowId`, which is the EAX config id. After update,
104
+ the new manifest `workflowId` is the only value that should be copied into `src/lib/sdk.ts`.
105
+ Do not invent IDs; let the AI design/sync/external workflow tools generate and persist them.
101
106
 
102
107
  ## Workflow Count Rule
103
108
 
@@ -137,18 +142,18 @@ Workflow must not do:
137
142
  - owner assignment or permissions;
138
143
  - app navigation, UI state, toast, or modal logic.
139
144
 
140
- If the product needs durable history, use entity persistence outside the workflow:
145
+ If the product needs durable history, design entity persistence outside the workflow:
141
146
 
142
- ```ts
143
- await runAiActionAndPersist({
144
- entity: howone.entities.Generation,
145
- input,
146
- createPending: (input) => ({ prompt: input.prompt, status: 'pending' }),
147
- run: (input) => howone.ai.generateImage.run(input),
148
- mapCompleted: ({ output }) => ({ status: 'completed', resultUrl: output.generated_image_url }),
149
- })
147
+ ```text
148
+ workflow input.prompt -> Generation.prompt
149
+ workflow output.imageUrl -> Generation.resultUrl
150
+ runtime failure -> Generation.errorMessage
151
+ runtime status -> Generation.status
150
152
  ```
151
153
 
154
+ After AI and database manifests are synced, app implementation can read the SDK track to wire the
155
+ runtime calls.
156
+
152
157
  ## Unsupported AI Behavior
153
158
 
154
159
  If a user explicitly requires behavior not available in the workflow service, stop that AI path.
@@ -202,5 +207,5 @@ Before editing files:
202
207
  - Input and output property names do not overlap.
203
208
  - Text output descriptions specify language behavior.
204
209
  - Persistence is modeled as entity schema, not workflow CRUD.
205
- - `workflowId`, `request_id`, and `workflowConfigID` are not guessed.
210
+ - `workflowId` / EAX `config_id` values are generated by tools and not guessed.
206
211
  - SDK binding will be generated only after manifest sync.