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.
- package/package.json +1 -1
- package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +33 -27
- package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +2 -2
- package/templates/vite/.howone/skills/howone/02-entity-schema/01-schema-design.md +9 -29
- package/templates/vite/.howone/skills/howone/02-entity-schema/02-schema-operations.md +166 -235
- package/templates/vite/.howone/skills/howone/02-entity-schema/03-access-models.md +151 -0
- package/templates/vite/.howone/skills/howone/02-entity-schema/04-query-contracts.md +123 -0
- package/templates/vite/.howone/skills/howone/02-entity-schema/05-ai-persistence-patterns.md +84 -201
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/01-ai-capability-architecture.md +37 -32
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/02-workflow-contract-rules.md +5 -4
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +89 -204
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/05-ai-feature-playbooks.md +8 -29
- package/templates/vite/.howone/skills/howone/04-app-sdk/01-client-setup.md +1 -2
- package/templates/vite/.howone/skills/howone/04-app-sdk/07-ai-action-calls.md +2 -2
- package/templates/vite/.howone/skills/howone/04-app-sdk/08-ai-manifest-handoff.md +6 -5
- package/templates/vite/.howone/skills/howone/04-app-sdk/09-extension-boundaries.md +1 -1
- 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
- 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
- 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
|
|
4
|
-
saved results,
|
|
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
|
-
|
|
7
|
-
|
|
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
|
|
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.
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
20
|
+
For every AI output field, decide:
|
|
21
21
|
|
|
22
|
-
| Question | Persist? |
|
|
22
|
+
| Question | Persist? | Entity design |
|
|
23
23
|
|---|---:|---|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
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
|
-
|
|
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
|
|
68
|
-
|
|
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
|
-
|
|
37
|
+
Required contract pieces:
|
|
71
38
|
|
|
72
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
64
|
+
## Minimal Generation History Entity
|
|
103
65
|
|
|
104
|
-
Use for image
|
|
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
|
-
|
|
142
|
-
Use an object field only for genuinely nested
|
|
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
|
|
105
|
+
## Structured Analysis Report Entity
|
|
145
106
|
|
|
146
|
-
Use when AI returns a
|
|
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
|
-
```
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
187
|
-
|
|
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
|
-
|
|
196
|
-
- public or scoped `SharedGeneration` entity for anonymous viewing.
|
|
151
|
+
## Public Share Split
|
|
197
152
|
|
|
198
|
-
|
|
153
|
+
For public AI result pages, split private history from public share data:
|
|
199
154
|
|
|
200
|
-
- private
|
|
201
|
-
- public
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
+
## Retry And Resume Fields
|
|
264
199
|
|
|
265
|
-
|
|
200
|
+
If retry is part of the product, persist enough input fields to rebuild the workflow request:
|
|
266
201
|
|
|
267
|
-
-
|
|
268
|
-
- selected mode/
|
|
269
|
-
- uploaded file
|
|
270
|
-
- status and
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
343
|
-
|
|
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
|
-
|
|
|
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
|
|
245
|
+
| Public submission to AI queue | public create only with anti-abuse constraints |
|
|
356
246
|
|
|
357
|
-
|
|
358
|
-
share entity or a curated public entity.
|
|
247
|
+
## Persistence Checklist
|
|
359
248
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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.
|
package/templates/vite/.howone/skills/howone/03-ai-capabilities/01-ai-capability-architecture.md
CHANGED
|
@@ -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 |
|
|
21
|
-
| SDK
|
|
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
|
-
|
|
39
|
-
|
|
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.
|
|
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`
|
|
57
|
-
8. Store returned
|
|
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.
|
|
60
|
-
11.
|
|
61
|
-
12.
|
|
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 `
|
|
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
|
-
-
|
|
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
|
-
`
|
|
90
|
+
Current `external-ai-capability` semantics:
|
|
93
91
|
|
|
94
92
|
```text
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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,
|
|
145
|
+
If the product needs durable history, design entity persistence outside the workflow:
|
|
141
146
|
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
210
|
+
- `workflowId` / EAX `config_id` values are generated by tools and not guessed.
|
|
206
211
|
- SDK binding will be generated only after manifest sync.
|