howone 0.1.22 → 0.1.23

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "howone",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "private": false,
5
5
  "description": "HowOne command line tools for creating app templates.",
6
6
  "type": "module",
@@ -18,6 +18,9 @@ export const entities = defineEntities({
18
18
 
19
19
  export const ai = defineAiActions({
20
20
  // Add generated AI action bindings here, for example:
21
+ // import { z } from "zod" and define Zod schemas before using defineAiAction.
22
+ // Do not paste JSON Schema objects from .howone/ai/manifest.json here directly.
23
+ // With outputSchema configured, howone.ai.<action>.run() returns the validated finalResult payload.
21
24
  // generateImage: defineAiAction("generateImage", {
22
25
  // inputSchema: generateImageInputSchema,
23
26
  // outputSchema: generateImageOutputSchema,
@@ -9,8 +9,10 @@ HowOne generated apps have three layers:
9
9
 
10
10
  1. **Backend contract**: dynamic entities, access rules, indexes, schema versions, and synced
11
11
  manifests.
12
- 2. **SDK binding**: `src/lib/sdk.ts` converts manifests into typed entity and AI clients.
13
- 3. **Frontend experience**: React/UI code calls the SDK and owns app-specific interaction design.
12
+ 2. **AI contract + workflow implementation**: AI capability contracts are versioned by HowOne;
13
+ external workflow create/update is submitted from synced AI manifests.
14
+ 3. **SDK binding**: `src/lib/sdk.ts` converts manifests into typed entity and AI clients.
15
+ 4. **Frontend experience**: React/UI code calls the SDK and owns app-specific interaction design.
14
16
 
15
17
  Do not skip the binding layer. The UI should call `src/lib/sdk.ts`, not raw entity names guessed
16
18
  from the user prompt.
@@ -60,6 +62,29 @@ When a request adds persistence or changes data shape:
60
62
  Do not preview a patch and then execute different single operations. Related schema changes for
61
63
  one feature should be grouped into one preview/apply cycle.
62
64
 
65
+ ## AI Feature Workflow
66
+
67
+ When a request adds an AI capability or changes an AI workflow:
68
+
69
+ 1. Read `04-ai/01-ai-capability-architecture.md` and
70
+ `04-ai/02-workflow-contract-rules.md`.
71
+ 2. Inspect current AI capability state.
72
+ 3. Design one complete AI capability patch for the feature.
73
+ 4. Preview the AI patch.
74
+ 5. Apply the exact previewed operations.
75
+ 6. Sync AI artifacts.
76
+ 7. Read `.howone/ai/manifest.json`.
77
+ 8. Submit external workflow create/update with `external-ai-capability`.
78
+ 9. Preserve returned request IDs for the status/background-task layer.
79
+ 10. Read `04-ai/03-ai-sdk-handoff.md`, `01-architect/02-manifest-codegen.md`, and
80
+ `03-sdk/07-ai-action-calls.md`.
81
+ 11. Update `src/lib/sdk.ts` from the synced manifest.
82
+ 12. Update the UI.
83
+ 13. Validate.
84
+
85
+ If generated output must be saved, design the AI capability first. Then derive persistence entities
86
+ from the synced AI `outputSchema`, plus only necessary request metadata.
87
+
63
88
  ## Auth Decision
64
89
 
65
90
  Use hosted auth when the app just needs login:
@@ -13,6 +13,11 @@ HowOne apps are driven by two backend-synced manifests:
13
13
 
14
14
  Sync tools (`sync_schema_artifacts`, `sync_ai_artifacts`) write the manifests. The coding agent reads the manifests and writes `src/lib/sdk.ts`.
15
15
 
16
+ For AI capabilities, external workflow create/update is submitted by `external-ai-capability` from
17
+ the synced manifest. Do not duplicate AI schemas in app code beyond generated zod/type bindings.
18
+ For workflow edits, `workflowConfigID` belongs to the external workflow operation; it is not an SDK
19
+ binding field.
20
+
16
21
  ---
17
22
 
18
23
  ## Reading `.howone/database/manifest.json`
@@ -157,6 +162,7 @@ export type CommentUpdate = Partial<CommentCreate>
157
162
  {
158
163
  "id": "generateStory",
159
164
  "name": "Generate Story",
165
+ "workflowId": "d69ab648-2c00-4d94-928e-01bd7b2a5bb2",
160
166
  "inputSchema": {
161
167
  "type": "object",
162
168
  "properties": {
@@ -179,6 +185,7 @@ export type CommentUpdate = Partial<CommentCreate>
179
185
  {
180
186
  "id": "translateText",
181
187
  "name": "Translate Text",
188
+ "workflowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
182
189
  "inputSchema": {
183
190
  "type": "object",
184
191
  "properties": {
@@ -219,12 +226,12 @@ export const generateStoryInputSchema = z.object({
219
226
  })
220
227
  export type GenerateStoryInput = z.infer<typeof generateStoryInputSchema>
221
228
 
222
- // Output type for manual unwrapping (do NOT put in defineAiAction as outputSchema)
223
- export type GenerateStoryOutput = {
224
- title: string
225
- content: string
226
- summary?: string
227
- }
229
+ export const generateStoryOutputSchema = z.object({
230
+ title: z.string(),
231
+ content: z.string(),
232
+ summary: z.string().optional(),
233
+ })
234
+ export type GenerateStoryOutput = z.infer<typeof generateStoryOutputSchema>
228
235
 
229
236
  // ── translateText ─────────────────────────────────────────────
230
237
  export const translateTextInputSchema = z.object({
@@ -235,6 +242,10 @@ export const translateTextInputSchema = z.object({
235
242
  export type TranslateTextInput = z.infer<typeof translateTextInputSchema>
236
243
  ```
237
244
 
245
+ Do not make required output fields optional to silence validation failures. Do not add
246
+ `.passthrough()` as a workaround for EAX execution envelopes. `defineAiAction` validates the
247
+ workflow `finalResult` payload when an `outputSchema` is configured.
248
+
238
249
  ---
239
250
 
240
251
  ## Full Generated `src/lib/sdk.ts`
@@ -303,11 +314,12 @@ export const generateStoryInputSchema = z.object({
303
314
  language: z.string().optional(),
304
315
  })
305
316
  export type GenerateStoryInput = z.infer<typeof generateStoryInputSchema>
306
- export type GenerateStoryOutput = {
307
- title: string
308
- content: string
309
- summary?: string
310
- }
317
+ export const generateStoryOutputSchema = z.object({
318
+ title: z.string(),
319
+ content: z.string(),
320
+ summary: z.string().optional(),
321
+ })
322
+ export type GenerateStoryOutput = z.infer<typeof generateStoryOutputSchema>
311
323
 
312
324
  export const translateTextInputSchema = z.object({
313
325
  text: z.string(),
@@ -315,10 +327,6 @@ export const translateTextInputSchema = z.object({
315
327
  formality: z.enum(['formal', 'informal']).optional(),
316
328
  })
317
329
  export type TranslateTextInput = z.infer<typeof translateTextInputSchema>
318
- export type TranslateTextOutput = {
319
- translatedText: string
320
- detectedLang?: string
321
- }
322
330
 
323
331
  // ═══════════════════════════════════════════════════════════════
324
332
  // CLIENT
@@ -344,9 +352,12 @@ export const entities = defineEntities({
344
352
 
345
353
  export const ai = defineAiActions({
346
354
  generateStory: defineAiAction('generateStory', {
355
+ workflowId: 'd69ab648-2c00-4d94-928e-01bd7b2a5bb2',
347
356
  inputSchema: generateStoryInputSchema,
357
+ outputSchema: generateStoryOutputSchema,
348
358
  }),
349
359
  translateText: defineAiAction('translateText', {
360
+ workflowId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
350
361
  inputSchema: translateTextInputSchema,
351
362
  }),
352
363
  })
@@ -371,7 +382,11 @@ Before finalising generated code, verify:
371
382
  - [ ] System fields are not present in create/update input types
372
383
  - [ ] Public query/write types are generated only from `access.public.allowedFilters`, `allowedSorts`, `requiredScopes`, and write permissions
373
384
  - [ ] Every AI action from `.howone/ai/manifest.json` has an `inputSchema` zod object
385
+ - [ ] Every AI action with manifest `outputSchema` has a matching zod `outputSchema`
386
+ - [ ] Every AI action binding includes the exact manifest `workflowId`
374
387
  - [ ] Required input fields are not `.optional()` in zod
388
+ - [ ] Required output fields are not `.optional()` in zod
389
+ - [ ] AI output schemas do not use `.passthrough()` to hide execution-envelope mismatches
375
390
  - [ ] AI action names match the manifest `id` exactly (case-sensitive)
376
391
  - [ ] `createClient` uses `import.meta.env.*` only
377
392
  - [ ] `withEntities` is applied before `withAiActions` in the composition chain
@@ -418,10 +433,7 @@ When the app generates data with AI and saves it to an entity:
418
433
  // Save it to Story entity which adds: authorId, status, wordCount
419
434
 
420
435
  async function generateAndSave(input: GenerateStoryInput, authorId: string) {
421
- const result = await howone.ai.generateStory.run(input)
422
- if (!result.success) throw new Error(result.errors.join(', '))
423
-
424
- const output = result.finalResult as GenerateStoryOutput
436
+ const output = await howone.ai.generateStory.run(input)
425
437
 
426
438
  return howone.entities.Story.create({
427
439
  title: output.title,
@@ -368,10 +368,8 @@ function AIGenerateButton({ topic }: { topic: string }) {
368
368
  }
369
369
  setLoading(true)
370
370
  try {
371
- const res = await howone.ai.generateStory.run({ topic, language: 'en' })
372
- if (res.success && res.finalResult) {
373
- setResult((res.finalResult as any).content)
374
- }
371
+ const output = await howone.ai.generateStory.run({ topic, language: 'en' })
372
+ setResult(output.content)
375
373
  } finally {
376
374
  setLoading(false)
377
375
  }
@@ -4,24 +4,31 @@
4
4
 
5
5
  **`src/lib/sdk.ts` must be generated from `.howone/ai/manifest.json`. Do not write it from memory or from generic examples.**
6
6
 
7
+ For AI capability and workflow design, read `04-ai/` first. This file is only for app-side SDK
8
+ bindings and runtime calls after the manifest exists.
9
+
7
10
  For every capability in `manifest.json`:
8
11
  1. Read `name`, `workflowId`, `inputSchema`, `outputSchema`
9
12
  2. Generate a zod schema from `inputSchema.properties`
10
- 3. Call `defineAiAction(name, { workflowId, inputSchema })` **`workflowId` is mandatory**
13
+ 3. Generate a zod schema from `outputSchema.properties` when an output schema exists
14
+ 4. Call `defineAiAction(name, { workflowId, inputSchema, outputSchema })` — **`workflowId` is mandatory**
11
15
 
12
16
  Without `workflowId`, the SDK falls back to using the action name as the URL segment. Action names are not UUIDs — the EAX server will reject the call with "invalid input syntax for type uuid".
13
17
 
18
+ Do not mark required manifest output fields as `.optional()` to silence validation. Do not add
19
+ `.passthrough()` as a workaround for execution envelopes. A typed `run()` validates and returns the
20
+ workflow `finalResult` payload, not the raw execution envelope.
21
+
14
22
  ## When to Write SDK Bindings
15
23
 
16
- **Do NOT write `defineAiAction` until `.howone/ai/manifest.json` contains a confirmed `workflowId`.**
24
+ **Do NOT write `defineAiAction` until `.howone/ai/manifest.json` contains the workflowId for the capability and the external workflow implementation has been submitted/confirmed by the workflow layer.**
17
25
 
18
26
  Correct sequence:
19
- 1. `ai_capability_design` — design the capability schema
27
+ 1. `ai-capability-design` — design the capability contract
20
28
  2. `sync_ai_artifacts` — sync manifest to disk
21
- 3. `external_ai_capability` — submit workflow to EAX; returns confirmed `workflowId`
22
- 4. Confirmed `workflowId` is written back to the backend capability record
23
- 5. `sync_ai_artifacts` again manifest now contains `workflowId`
24
- 6. Read `.howone/ai/manifest.json` → write `src/lib/sdk.ts` with `workflowId`
29
+ 3. `external-ai-capability` — submit workflow create/update to EAX from the synced manifest
30
+ 4. Workflow status/background-task layer confirms readiness and captures `workflowConfigID` for future edits
31
+ 5. Read `.howone/ai/manifest.json` write `src/lib/sdk.ts` with `workflowId`
25
32
 
26
33
  Building without errors does **not** mean the AI workflow binding is correct. A missing `workflowId` causes a runtime UUID error at the EAX execution call.
27
34
 
@@ -45,7 +52,7 @@ Building without errors does **not** mean the AI workflow binding is correct. A
45
52
  type AiActionConfig<TInput, TOutput> = {
46
53
  workflowId: string // REQUIRED — UUID from manifest.json. Without this, SDK uses action name as URL segment (not a UUID → EAX rejects).
47
54
  inputSchema?: z.ZodType<TInput> // validates input before calling the workflow
48
- outputSchema?: z.ZodType<TOutput> // validates the full ExecutionResult envelope (see caution below)
55
+ outputSchema?: z.ZodType<TOutput> // validates the workflow finalResult payload for run()
49
56
  mode?: 'run' | 'stream' | 'events' // default: supports all three modes
50
57
  }
51
58
  ```
@@ -120,7 +127,7 @@ export const ai = defineAiActions({
120
127
  })
121
128
  ```
122
129
 
123
- ### Action with typed output (unwrap finalResult manually)
130
+ ### Action with typed output
124
131
 
125
132
  ```ts
126
133
  export const generateStoryOutputSchema = z.object({
@@ -130,13 +137,11 @@ export const generateStoryOutputSchema = z.object({
130
137
  })
131
138
  export type GenerateStoryOutput = z.infer<typeof generateStoryOutputSchema>
132
139
 
133
- // Do NOT put outputSchema in defineAiAction unless it matches the full
134
- // AiResult envelope. Instead, cast finalResult after run():
135
140
  export const ai = defineAiActions({
136
141
  generateStory: defineAiAction('generateStory', {
137
142
  workflowId: 'd69ab648-2c00-4d94-928e-01bd7b2a5bb2', // ← from manifest.json
138
143
  inputSchema: generateStoryInputSchema,
139
- // outputSchema: omit — cast manually
144
+ outputSchema: generateStoryOutputSchema,
140
145
  }),
141
146
  })
142
147
  ```
@@ -171,25 +176,21 @@ export const ai = defineAiActions({
171
176
 
172
177
  ## Calling AI Actions
173
178
 
174
- ### run() — await the full result
179
+ ### run() — typed action result
175
180
 
176
181
  ```ts
177
182
  import howone, { type GenerateStoryInput, type GenerateStoryOutput } from '@/lib/sdk'
178
183
 
179
184
  async function generateStory(input: GenerateStoryInput) {
180
- const result = await howone.ai.generateStory.run(input)
181
- // result is AiResult
182
-
183
- if (!result.success) {
184
- throw new Error(result.errors.join(', '))
185
- }
186
-
187
- // Cast finalResult to your output type
188
- const output = result.finalResult as GenerateStoryOutput
185
+ const output = await howone.ai.generateStory.run(input)
186
+ // output is GenerateStoryOutput when outputSchema is configured.
189
187
  return output
190
188
  }
191
189
  ```
192
190
 
191
+ When an action has `outputSchema`, `run()` returns the validated workflow `finalResult` payload.
192
+ When an action omits `outputSchema`, `run()` returns the raw `AiResult` execution envelope.
193
+
193
194
  ### run() — with SSE callbacks
194
195
 
195
196
  ```ts
@@ -362,7 +363,7 @@ type SSEExecutionOptions = {
362
363
  // Called if an error occurs
363
364
  onError?: (error: Error) => void
364
365
 
365
- // Called when the workflow completes (same as awaiting run())
366
+ // Called when the workflow completes with the raw execution envelope
366
367
  onComplete?: (result: AiResult) => void
367
368
 
368
369
  // Abort signal — connect to an AbortController for cancellation
@@ -406,15 +407,11 @@ When AI-generated content should be saved to an entity:
406
407
 
407
408
  ```ts
408
409
  // 1. Run the AI action
409
- const result = await howone.ai.generateStory.run({
410
+ const output = await howone.ai.generateStory.run({
410
411
  topic: 'Dragons and magic',
411
412
  ageRange: '6-8',
412
413
  })
413
414
 
414
- if (!result.success) throw new Error(result.errors.join(', '))
415
-
416
- const output = result.finalResult as GenerateStoryOutput
417
-
418
415
  // 2. Save to entity
419
416
  const saved = await howone.entities.Story.create({
420
417
  title: output.title,
@@ -448,9 +445,8 @@ function GenerateStoryButton({ input }: { input: GenerateStoryInput }) {
448
445
  setLoading(true)
449
446
  setError(null)
450
447
  try {
451
- const res = await howone.ai.generateStory.run(input)
452
- if (!res.success) throw new Error(res.errors.join(', '))
453
- setResult(res.finalResult as GenerateStoryOutput)
448
+ const output = await howone.ai.generateStory.run(input)
449
+ setResult(output)
454
450
  } catch (err) {
455
451
  if (err instanceof AiSchemaValidationError) {
456
452
  setError(`Validation error: ${err.issues.map(i => i.message).join(', ')}`)
@@ -522,10 +518,11 @@ function StreamingStoryGenerator({ input }: { input: GenerateStoryInput }) {
522
518
  | Mistake | Correct Pattern |
523
519
  |---|---|
524
520
  | `defineAiAction('generateStory', { inputSchema })` — no `workflowId` | Always include `workflowId` from `manifest.json`; SDK falls back to action name, which is not a UUID → EAX rejects |
525
- | Writing `src/lib/sdk.ts` before `sync_ai_artifacts` has a confirmed `workflowId` | Run `external_ai_capability` → `sync_ai_artifacts` first; only write bindings after manifest has `workflowId` |
521
+ | Writing `src/lib/sdk.ts` before `.howone/ai/manifest.json` has a workflowId | Run `ai-capability-design` → `sync_ai_artifacts` `external-ai-capability`; only write bindings from the synced manifest |
526
522
  | Hardcoding `workflowId` from memory or guessing | Always read from `.howone/ai/manifest.json` — copy the exact UUID |
527
523
  | `howone.ai.run.generateStory(input)` | `howone.ai.generateStory.run(input)` |
528
524
  | Action named `run`, `stream`, or `events` | Rename to e.g. `executeWorkflow`, `streamContent` |
529
- | `outputSchema: z.object({ title: z.string() })` expecting `finalResult` shape | Omit `outputSchema`; cast `result.finalResult` manually |
530
- | Not checking `result.success` before using `finalResult` | Always check `if (!result.success) throw new Error(...)` |
525
+ | Passing raw JSON Schema from manifest into `defineAiAction` | Convert JSON Schema fields to Zod first |
526
+ | Making every output field `.optional()` or adding `.passthrough()` after validation fails | Keep manifest-required output fields required; inspect `AiSchemaValidationError.issues` and fix the contract/workflow mismatch |
527
+ | Reading `raw.finalResult`, `raw.data.result`, or `raw.result` after a typed `.run()` | Use the returned value directly when `outputSchema` is configured |
531
528
  | Calling `howone.ai.generateStory.run(input)` inside JSX render | Move to event handler or useEffect |
@@ -0,0 +1,142 @@
1
+ # AI Capability Architecture
2
+
3
+ Use this reference when a HowOne app needs AI generation, editing, analysis, research, or other
4
+ workflow-backed behavior.
5
+
6
+ ## Core Split
7
+
8
+ HowOne AI has four separate layers:
9
+
10
+ 1. **Capability contract**: `ai-capability-design` owns the name, description, input schema, output
11
+ schema, output entity name, workflow ID, versions, preview, apply, restore, and manifest sync.
12
+ 2. **External workflow implementation**: `external-ai-capability` submits create/update operations
13
+ to the workflow service using the synced contract from `.howone/ai/manifest.json`.
14
+ 3. **Workflow status/background layer**: status checking, request tracking, completion, failure, and
15
+ durable background-task behavior are handled outside the contract tool. Preserve request IDs and
16
+ workflow config IDs for that layer.
17
+ 4. **SDK binding and UI**: `src/lib/sdk.ts` is generated from `.howone/ai/manifest.json`, then the
18
+ app calls `howone.ai.<capability>.run()`, `.stream()`, or `.events()`.
19
+
20
+ Do not collapse these layers. The common failure is putting workflow implementation, persistence,
21
+ and UI details into the capability schema.
22
+
23
+ ## Source of Truth
24
+
25
+ ```text
26
+ agent proposal != source of truth
27
+ applied AI capability version = source of truth
28
+ .howone/ai/manifest.json = local source for codegen
29
+ src/lib/sdk.ts = generated binding output
30
+ external workflow service = implementation builder/editor
31
+ ```
32
+
33
+ The agent proposes a contract. The backend validates and versions it. The sync tool writes the
34
+ manifest. App code is generated from the manifest.
35
+
36
+ ## Standard AI Feature Workflow
37
+
38
+ 1. Inspect current AI state with `ai-capability-design`.
39
+ 2. Design one complete capability patch for the feature.
40
+ 3. Preview the patch.
41
+ 4. Apply the same operations.
42
+ 5. Sync AI artifacts.
43
+ 6. Read `.howone/ai/manifest.json`.
44
+ 7. Submit external workflow create/update with `external-ai-capability`.
45
+ 8. Preserve returned `requestIds` for the status/background-task layer.
46
+ 9. Generate or update `src/lib/sdk.ts` from the manifest.
47
+ 10. Implement UI calls through `howone.ai.*`.
48
+ 11. If generated output must be stored, design database entities after the AI output schema exists.
49
+
50
+ Do not preview a patch and then submit different operations. Do not restate schemas in
51
+ `external-ai-capability`; it loads the manifest.
52
+
53
+ ## Unsupported AI Requests
54
+
55
+ If the user explicitly asks for AI capability and the feature cannot be implemented with the
56
+ available workflow service capabilities, stop the AI implementation task and report the gap.
57
+
58
+ Do not:
59
+
60
+ - fake the AI feature with static mock data.
61
+ - implement a frontend-only placeholder that pretends AI works.
62
+ - design a workflow that assumes unavailable tools, providers, APIs, or private datasets.
63
+ - silently drop the AI part and continue building a non-AI version.
64
+ - move forbidden work such as database CRUD, auth, upload handling, payments, or app state into the
65
+ workflow.
66
+
67
+ The correct response is to state which requested AI behavior is unsupported, which capability is
68
+ missing or which rule blocks it, and what narrower supported alternative could be built.
69
+
70
+ Only continue implementation when the remaining feature still satisfies the user's intent after
71
+ the unsupported AI capability is removed or narrowed.
72
+
73
+ ## Create vs Update
74
+
75
+ Use `external-ai-capability` with `mode: "create"` when a capability has no external implementation.
76
+ The tool reads `workflowId`, `inputSchema`, `outputSchema`, and `outputEntityName` from the synced
77
+ manifest.
78
+
79
+ Use `mode: "update"` when the external workflow implementation needs to change. Updates require:
80
+
81
+ - `capabilityName`
82
+ - `workflowConfigID`
83
+ - `updatePrompt`
84
+
85
+ `workflowConfigID` must come from a confirmed workflow status result, specifically
86
+ `payload.workflow_details.new_workflow_config_id`. Do not invent it, omit it, or substitute
87
+ `workflowId`.
88
+
89
+ If the input/output schema changes, update the capability contract first, sync the manifest, then
90
+ submit the external workflow update.
91
+
92
+ ## Persistence Boundary
93
+
94
+ AI workflows produce outputs. They do not persist app state.
95
+
96
+ Workflow can do:
97
+
98
+ - text generation, summarization, translation, classification, extraction
99
+ - image, video, and audio generation/editing/analysis
100
+ - web research and page crawling
101
+ - financial or academic retrieval
102
+ - file generation or file reading through URLs
103
+
104
+ Workflow must not do:
105
+
106
+ - database create/read/update/delete
107
+ - authentication or session handling
108
+ - upload handling
109
+ - payment processing
110
+ - app state management
111
+ - owner assignment or record permissions
112
+
113
+ If the app saves AI output, derive entity fields from `outputSchema` and add only necessary request
114
+ metadata such as prompt, selected options, status, timestamps, or references.
115
+
116
+ ## Workflow Count
117
+
118
+ Default rule: one user-facing AI feature maps to one capability and one workflow.
119
+
120
+ Examples:
121
+
122
+ - Story + illustration generator: one workflow that returns story text and generated image URLs.
123
+ - Image edit feature: one workflow that accepts source image URL and edit instruction, returns
124
+ edited image URL.
125
+ - News briefing: one workflow that searches and summarizes, returns briefing and source links.
126
+
127
+ RAG is the exception:
128
+
129
+ - indexing workflow: ingests documents and creates retrieval/indexing artifacts.
130
+ - query workflow: answers questions from the indexed knowledge base.
131
+
132
+ Do not split normal sequential AI steps into multiple capabilities unless they are independently
133
+ triggered product features.
134
+
135
+ ## Capability Naming
136
+
137
+ Use stable JavaScript-safe identifiers for capability names, for example `generateIllustration` or
138
+ `summarizeDocument`. Do not use display labels as identifiers.
139
+
140
+ Avoid names that collide with action methods: `run`, `stream`, `events`.
141
+
142
+ Use `outputEntityName` only when the generated output has a natural persisted record shape.
@@ -0,0 +1,169 @@
1
+ # Workflow Contract Rules
2
+
3
+ Use this reference when designing `inputSchema`, `outputSchema`, and capability descriptions for
4
+ HowOne AI workflows.
5
+
6
+ ## Loose JSON Schema
7
+
8
+ Workflow schemas should be loose enough for an AI workflow engine.
9
+
10
+ Rules:
11
+
12
+ - Require only essential inputs.
13
+ - Mark non-essential options optional.
14
+ - Prefer `string` with a clear description over restrictive enums when user values are open-ended.
15
+ - Avoid `minLength`, `maxLength`, `pattern`, and narrow validation unless there is a concrete
16
+ technical reason.
17
+ - Avoid nested objects when a simple described string is enough.
18
+ - Avoid exposing implementation knobs the user does not need.
19
+
20
+ Good:
21
+
22
+ ```json
23
+ {
24
+ "tone": {
25
+ "type": "string",
26
+ "description": "Desired writing tone, e.g. formal, casual, humorous, empathetic, professional."
27
+ }
28
+ }
29
+ ```
30
+
31
+ Use enums only for truly closed domains.
32
+
33
+ ## URLs, Not Raw Files
34
+
35
+ All file exchange uses URLs.
36
+
37
+ Rules:
38
+
39
+ - File inputs are strings with `format: "uri"`, such as `document_url`, `source_image_url`, or
40
+ `audio_file_url`.
41
+ - File outputs are strings with `format: "uri"`, such as `generated_image_url`, `edited_video_url`,
42
+ or `result_pdf_url`.
43
+ - Never use base64, raw bytes, inline file content, or `contentEncoding`.
44
+ - The app uploads user files first, then passes the URL to the workflow.
45
+
46
+ ## Output Minimalism
47
+
48
+ The workflow will try to fill every output field. Only ask for fields the product needs.
49
+
50
+ Usually forbidden unless explicitly requested:
51
+
52
+ - timestamps and processing time
53
+ - file size, MIME type, encoding, dimensions, frame rate, bitrate
54
+ - confidence scores and bounding boxes
55
+ - model, provider, version, cost, or internal execution metadata
56
+ - style/tone metadata when the user only asked for an asset
57
+
58
+ Examples:
59
+
60
+ | User Need | Good Output | Avoid |
61
+ |---|---|---|
62
+ | Summarize a document | `summary` | `summary`, `word_count`, `reading_time` |
63
+ | Generate an image | `generated_image_url` | `generated_image_url`, `model_used`, `color_palette` |
64
+ | OCR an image | `extracted_text` | `extracted_text`, `confidence_score`, `bounding_boxes` |
65
+
66
+ ## No Input/Output Name Overlap
67
+
68
+ Input and output property names must not overlap. The workflow engine uses a shared parameter
69
+ namespace.
70
+
71
+ Bad:
72
+
73
+ ```json
74
+ {
75
+ "inputSchema": { "properties": { "text": { "type": "string" } } },
76
+ "outputSchema": { "properties": { "text": { "type": "string" } } }
77
+ }
78
+ ```
79
+
80
+ Good:
81
+
82
+ ```json
83
+ {
84
+ "inputSchema": { "properties": { "source_text": { "type": "string" } } },
85
+ "outputSchema": { "properties": { "translated_text": { "type": "string" } } }
86
+ }
87
+ ```
88
+
89
+ Common pairs:
90
+
91
+ - `source_text` -> `translated_text`
92
+ - `source_content` -> `summary`
93
+ - `source_image_url` -> `edited_image_url`
94
+ - `description_prompt` -> `generated_image_url`
95
+
96
+ ## Output Language
97
+
98
+ Every text output field description must specify the language rule.
99
+
100
+ Use one of these patterns:
101
+
102
+ - fixed: "Summary in English."
103
+ - input-driven: "Translated text in the target language specified by `target_language`."
104
+ - source-driven: "Summary in the same language as the source document."
105
+ - mixed: "Extracted text, which may contain mixed Chinese and English content."
106
+
107
+ Do not write vague descriptions like "The translated text."
108
+
109
+ ## Description Says What, Not How
110
+
111
+ `capability.description` describes the user outcome.
112
+
113
+ Do:
114
+
115
+ ```json
116
+ {
117
+ "description": "Searches the web for the latest news on a topic and produces a structured briefing with source links."
118
+ }
119
+ ```
120
+
121
+ Do not:
122
+
123
+ ```json
124
+ {
125
+ "description": "First calls search_web, then summarizes each article with an LLM, then saves JSON."
126
+ }
127
+ ```
128
+
129
+ Never mention internal tool names, step sequences, providers, or model names in the capability
130
+ description.
131
+
132
+ ## Available Capability Families
133
+
134
+ Design only around capabilities available to the workflow service:
135
+
136
+ - web search and page crawling
137
+ - image generation, editing, analysis, OCR
138
+ - video generation, editing, concatenation, frame extraction
139
+ - audio generation, recognition, merging
140
+ - financial price history retrieval
141
+ - academic paper search and bibliography
142
+ - file storage/read for JSON, YAML, CSV, PDF, Markdown, TXT
143
+ - email sending
144
+ - RSS feed fetching
145
+ - Airbnb search
146
+ - Hacker News search
147
+
148
+ If the requested product needs unavailable functionality, redesign the feature around available
149
+ capabilities or exclude it explicitly.
150
+
151
+ When the user explicitly requires the unavailable AI behavior, terminate the AI task instead of
152
+ building a fake or silently degraded implementation. Report the unsupported requirement and the
153
+ closest supported alternative.
154
+
155
+ ## External Data Assumptions
156
+
157
+ Do not assume the user can provide external datasets unless they explicitly say so.
158
+
159
+ For "stock analysis", use a workflow input such as `trading_symbol` and let the workflow retrieve
160
+ history. Do not require `stock_data_csv_url` unless the user says they have a CSV.
161
+
162
+ For "latest news", use web search inside the workflow. Do not ask the user to provide article URLs
163
+ unless that is the product requirement.
164
+
165
+ ## MVP Limit
166
+
167
+ Keep AI app generation to at most five core features. Exclude feedback forms, onboarding tutorials,
168
+ notification settings, export, sharing, personalization, and preferences unless the user explicitly
169
+ requests them.
@@ -0,0 +1,80 @@
1
+ # AI SDK Handoff
2
+
3
+ Use this reference after AI capability artifacts have been synced and app code needs to call the
4
+ workflow.
5
+
6
+ ## Binding Source
7
+
8
+ Generate `src/lib/sdk.ts` from `.howone/ai/manifest.json`. Do not write AI bindings from memory or
9
+ from the original user prompt.
10
+
11
+ For each manifest capability:
12
+
13
+ 1. Read `name`.
14
+ 2. Read `workflowId`.
15
+ 3. Read `inputSchema`.
16
+ 4. Read `outputSchema`.
17
+ 5. Generate zod input and output schemas from the JSON Schema fields.
18
+ 6. Bind with `defineAiAction(name, { workflowId, inputSchema, outputSchema })`.
19
+
20
+ `workflowId` is mandatory. Without it, the SDK falls back to the action name as the execution URL
21
+ segment, which is not a workflow UUID.
22
+
23
+ ## Output Handling
24
+
25
+ For a typed action with `outputSchema`, `.run()` returns the validated workflow output payload.
26
+ The SDK unwraps the EAX execution envelope and validates `finalResult` internally.
27
+
28
+ Use this pattern:
29
+
30
+ ```ts
31
+ const output = await howone.ai.generateSummary.run(input)
32
+ // output is GenerateSummaryOutput when outputSchema is configured.
33
+ ```
34
+
35
+ Do not read `raw.finalResult`, `raw.result`, or `raw.data.result` from a typed action result. Those
36
+ paths are execution/SSE internals. App code should use the value returned by `.run()` directly.
37
+
38
+ Do not make every output field `.optional()` or add `.passthrough()` to hide validation errors.
39
+ Required fields in the manifest must stay required in Zod. If validation fails, inspect
40
+ `AiSchemaValidationError.issues` and fix the capability contract or workflow output mapping.
41
+
42
+ ## UI State
43
+
44
+ AI calls should run in event handlers, effects, or explicit async actions. Never call
45
+ `howone.ai.*.run()` inside JSX render.
46
+
47
+ Recommended UI states:
48
+
49
+ - idle
50
+ - running
51
+ - succeeded
52
+ - failed
53
+ - cancelled when using streaming
54
+
55
+ For streaming, keep the `AiSession` in a ref and call `cancel()` from the UI.
56
+
57
+ ## Persistence Handoff
58
+
59
+ If the app stores generated output:
60
+
61
+ 1. Run the AI workflow.
62
+ 2. Use the typed output returned by `.run()`.
63
+ 3. Write the resulting data through `howone.entities.*`.
64
+
65
+ Do not ask the workflow to write to the database. Do not pass owner fields for authenticated
66
+ user-owned entities; HowOne derives ownership from the JWT.
67
+
68
+ ## Workflow Edit Handoff
69
+
70
+ When editing an external workflow implementation later, use `external-ai-capability` with:
71
+
72
+ - `mode: "update"`
73
+ - `capabilityName`
74
+ - `workflowConfigID`
75
+ - `updatePrompt`
76
+
77
+ `workflowConfigID` is not `workflowId`. It comes from a confirmed workflow status result:
78
+ `payload.workflow_details.new_workflow_config_id`.
79
+
80
+ If the schema changed, update and sync the capability contract before submitting the workflow edit.
@@ -1,12 +1,17 @@
1
1
  ---
2
2
  name: howone-sdk
3
- description: Design and implement HowOne generated apps correctly. Use when planning HowOne app architecture, designing backend entity schema/access, wiring @howone/sdk in src/lib/sdk.ts, implementing auth flows, calling howone.entities.* or howone.public.entities.*, generating SDK bindings from .howone manifests, or reviewing HowOne app runtime integration.
3
+ description: Required operating manual for HowOne generated apps. Use for backend schema/API design, frontend UI implementation that reads or writes HowOne data, AI capability/workflow design, SDK binding/codegen from .howone manifests, auth, uploads, public/private access, and any code that calls @howone/sdk, howone.entities.*, howone.public.entities.*, or howone.ai.*.
4
4
  ---
5
5
 
6
6
  # HowOne App Architecture Skill
7
7
 
8
- This skill is one HowOne app-building skill with numbered internal tracks. Load the smallest track
9
- that matches the task; do not read every file by default.
8
+ This skill is the operating manual for generated HowOne apps. Use it before making platform
9
+ decisions for backend data, frontend data access, auth, AI capabilities, SDK bindings, or generated
10
+ app code that depends on HowOne runtime behavior.
11
+
12
+ Load the smallest set of numbered references that match the task, but do not skip this skill for
13
+ backend schema design, frontend implementation, or AI features. The references define the contracts
14
+ that app code must follow.
10
15
 
11
16
  ## Active Tracks
12
17
 
@@ -15,19 +20,22 @@ that matches the task; do not read every file by default.
15
20
  | `01-architect/` | App generation + manifest flow | End-to-end HowOne app generation flow: when to design backend, when to sync manifests, when to update SDK bindings, and how to choose auth/data posture. |
16
21
  | `02-database/` | Backend schema + data access design | Entity schema contract, schema operations, access modes, owner/public data posture, indexes, versions, and guardrails. |
17
22
  | `03-sdk/` | Frontend SDK usage | `src/lib/sdk.ts`, auth, React provider, entity calls, public data, uploads, raw HTTP, AI action calls. |
18
-
19
- Reserved directory:
20
-
21
- - `04-ai/` is intentionally present for future AI capability design. Do not use it yet. For now,
22
- only use `03-sdk/07-ai-action-calls.md` when wiring an already-synced AI action into app code.
23
+ | `04-ai/` | AI capability + workflow design | HowOne AI capability contracts, external workflow create/update submission, workflow schema rules, persistence boundaries, and SDK handoff. |
23
24
 
24
25
  ## Routing
25
26
 
26
- Read references by task shape:
27
+ Read references by task shape. Prefer exact references over generic examples.
27
28
 
28
29
  - New HowOne app or broad feature planning: read `01-architect/01-app-generation.md`.
30
+ - Any feature touching backend data, frontend data access, or saved records: read the architect
31
+ flow first, then the relevant database and SDK references below.
32
+ - Any feature touching AI generation, AI workflow behavior, or AI outputs: read the AI references
33
+ first, then the SDK handoff/action-call references.
34
+ - AI capability, AI workflow generation/editing, or full-stack AI feature planning: read
35
+ `04-ai/01-ai-capability-architecture.md` and `04-ai/02-workflow-contract-rules.md`.
29
36
  - Backend database/schema creation or change: read `02-database/01-schema-design.md` and
30
37
  `02-database/02-schema-operations.md`.
38
+ - AI output persistence: read the `04-ai/` files first, then `02-database/01-schema-design.md`.
31
39
  - After schema sync, when app code must call the entity: read
32
40
  `01-architect/02-manifest-codegen.md` and `03-sdk/02-entity-operations.md`.
33
41
  - Custom login page, Email OTP, Phone OTP, Google, GitHub, token persistence, repeated login,
@@ -38,6 +46,28 @@ Read references by task shape:
38
46
  `03-sdk/02-entity-operations.md`, and `01-architect/02-manifest-codegen.md`.
39
47
  - File upload: read `03-sdk/05-file-upload.md`.
40
48
  - Raw HTTP escape hatch: read `03-sdk/06-raw-http.md`.
49
+ - App-side AI action calls after `.howone/ai/manifest.json` exists: read
50
+ `03-sdk/07-ai-action-calls.md`.
51
+
52
+ ## Reference Selection Protocol
53
+
54
+ Before writing code, classify the touched surfaces:
55
+
56
+ | Touched surface | Required references |
57
+ |---|---|
58
+ | New app, feature architecture, or uncertain data posture | `01-architect/01-app-generation.md` |
59
+ | Entity/schema/access/index change | `02-database/01-schema-design.md`, `02-database/02-schema-operations.md` |
60
+ | Existing synced manifest to TypeScript bindings | `01-architect/02-manifest-codegen.md` |
61
+ | UI reads/writes entities | `03-sdk/01-client-setup.md`, `03-sdk/02-entity-operations.md` |
62
+ | Public read/share flow | `02-database/03-data-access-patterns.md`, `03-sdk/02-entity-operations.md` |
63
+ | Auth/session/login behavior | `03-sdk/03-auth.md`, `03-sdk/04-react-integration.md` |
64
+ | AI capability or workflow design | `04-ai/01-ai-capability-architecture.md`, `04-ai/02-workflow-contract-rules.md` |
65
+ | AI manifest handoff to app code | `04-ai/03-ai-sdk-handoff.md`, `03-sdk/07-ai-action-calls.md` |
66
+ | File upload | `03-sdk/05-file-upload.md` |
67
+ | Raw HTTP escape hatch | `03-sdk/06-raw-http.md` |
68
+
69
+ If a task spans multiple surfaces, read one reference from each surface before editing. Do not
70
+ invent API parameters, response shapes, owner fields, workflow IDs, or output paths from memory.
41
71
 
42
72
  ## Core Workflow
43
73
 
@@ -52,6 +82,20 @@ For app features that touch backend data:
52
82
  6. Implement frontend calls using the SDK track.
53
83
  7. Validate with the app's typecheck/build.
54
84
 
85
+ For app features that touch AI:
86
+
87
+ 1. Decide the AI feature boundary in the AI track: one workflow per feature, except RAG uses
88
+ indexing and query workflows.
89
+ 2. Design or update the AI capability contract with `ai-capability-design`.
90
+ 3. Preview/apply the same AI patch, then sync AI artifacts.
91
+ 4. Submit external workflow create/update with `external-ai-capability`.
92
+ 5. Preserve returned request IDs for the workflow status/background-task layer.
93
+ 6. Read `.howone/ai/manifest.json`.
94
+ 7. Generate or update app SDK bindings from the manifest.
95
+ 8. Implement frontend calls using `howone.ai.*`.
96
+ 9. If outputs are saved, derive entity fields from the AI `outputSchema`, then follow the database
97
+ workflow.
98
+
55
99
  For frontend-only SDK work:
56
100
 
57
101
  1. Read existing `src/lib/sdk.ts`.
@@ -73,3 +117,10 @@ For frontend-only SDK work:
73
117
  reads or writes.
74
118
  - For custom in-app login, use `HowOneProvider auth="none"`, call
75
119
  `howone.auth.setToken(token)` on success, and verify startup session with `await howone.me()`.
120
+ - AI workflows are implementation, not persistence. Never put CRUD, auth, upload handling, or app
121
+ state management into the workflow capability contract.
122
+ - For external workflow edits, pass `workflowConfigID` from a confirmed workflow status result; do
123
+ not invent it or omit it.
124
+ - If the user explicitly requires AI behavior that the available workflow service cannot support,
125
+ stop that AI implementation path and report the unsupported requirement instead of faking,
126
+ silently omitting, or replacing the AI capability with static behavior.
@@ -18,6 +18,9 @@ export const entities = defineEntities({
18
18
 
19
19
  export const ai = defineAiActions({
20
20
  // Add generated AI action bindings here, for example:
21
+ // import { z } from "zod" and define Zod schemas before using defineAiAction.
22
+ // Do not paste JSON Schema objects from .howone/ai/manifest.json here directly.
23
+ // With outputSchema configured, howone.ai.<action>.run() returns the validated finalResult payload.
21
24
  // generateImage: defineAiAction("generateImage", {
22
25
  // workflowId: "<workflow-uuid>", // workflow ID for this capability
23
26
  // inputSchema: generateImageInputSchema,