@pikku/skills 0.12.22 → 0.12.26

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 (106) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/dist/skills.gen.js +1 -1
  3. package/package.json +1 -1
  4. package/skills/pikku-a11y/SKILL.md +59 -0
  5. package/skills/pikku-addon/SKILL.md +2 -2
  6. package/skills/pikku-agent/SKILL.md +67 -316
  7. package/skills/pikku-agent/references/agents.md +299 -0
  8. package/skills/{pikku-ai-vercel/SKILL.md → pikku-agent/references/runner-vercel.md} +2 -24
  9. package/skills/{pikku-ai-voice/SKILL.md → pikku-agent/references/voice.md} +1 -22
  10. package/skills/pikku-architect/SKILL.md +265 -0
  11. package/skills/pikku-auth/SKILL.md +89 -0
  12. package/skills/{pikku-better-auth/SKILL.md → pikku-auth/references/better-auth.md} +126 -34
  13. package/skills/{pikku-jose/SKILL.md → pikku-auth/references/jose.md} +1 -23
  14. package/skills/{pikku-machine-auth/SKILL.md → pikku-auth/references/machine-auth.md} +0 -23
  15. package/skills/pikku-auth/references/permissions.md +261 -0
  16. package/skills/{pikku-security/SKILL.md → pikku-auth/references/sessions.md} +0 -20
  17. package/skills/pikku-build/SKILL.md +88 -0
  18. package/skills/{pikku-build-app/SKILL.md → pikku-build/references/app.md} +76 -24
  19. package/skills/{pikku-feature/SKILL.md → pikku-build/references/feature.md} +1 -8
  20. package/skills/{pikku-build-app → pikku-build}/references/multi-app.md +56 -1
  21. package/skills/{pikku-build-platform/SKILL.md → pikku-build/references/platform.md} +22 -37
  22. package/skills/{pikku-template-clone/SKILL.md → pikku-build/references/post-clone.md} +0 -6
  23. package/skills/{pikku-build-quick/SKILL.md → pikku-build/references/quick.md} +4 -20
  24. package/skills/{pikku-build-app → pikku-build}/references/ship.md +9 -3
  25. package/skills/pikku-concepts/SKILL.md +72 -7
  26. package/skills/pikku-concepts/references/concept-mapping.md +8 -8
  27. package/skills/pikku-deploy/SKILL.md +158 -0
  28. package/skills/{pikku-deploy-azure/SKILL.md → pikku-deploy/references/azure.md} +18 -50
  29. package/skills/pikku-deploy/references/cloudflare.md +104 -0
  30. package/skills/pikku-deploy/references/express.md +92 -0
  31. package/skills/{pikku-deploy-fastify/SKILL.md → pikku-deploy/references/fastify.md} +8 -32
  32. package/skills/{pikku-deploy-lambda/SKILL.md → pikku-deploy/references/lambda.md} +6 -27
  33. package/skills/{pikku-deploy-nextjs/SKILL.md → pikku-deploy/references/nextjs.md} +9 -33
  34. package/skills/pikku-deploy/references/uws.md +72 -0
  35. package/skills/pikku-deploy/references/ws.md +75 -0
  36. package/skills/pikku-emails/SKILL.md +3 -2
  37. package/skills/pikku-fabric/SKILL.md +47 -20
  38. package/skills/{pikku-fabric-debug/SKILL.md → pikku-fabric/references/debugging.md} +0 -6
  39. package/skills/pikku-i18n/SKILL.md +62 -207
  40. package/skills/{pikku-paraglide/SKILL.md → pikku-i18n/references/enum-labels.md} +0 -6
  41. package/skills/pikku-i18n/references/messages.md +218 -0
  42. package/skills/{pikku-rtl/SKILL.md → pikku-i18n/references/rtl.md} +3 -9
  43. package/skills/pikku-knowledge/SKILL.md +15 -0
  44. package/skills/pikku-kysely/SKILL.md +13 -13
  45. package/skills/pikku-list-query/SKILL.md +163 -0
  46. package/skills/pikku-meta/SKILL.md +58 -130
  47. package/skills/{pikku-deps/SKILL.md → pikku-meta/references/audit.md} +1 -17
  48. package/skills/pikku-meta/references/meta.md +114 -0
  49. package/skills/{pikku-versioning/SKILL.md → pikku-meta/references/versioning.md} +0 -26
  50. package/skills/pikku-middleware/SKILL.md +5 -5
  51. package/skills/pikku-n8n-import/SKILL.md +0 -1
  52. package/skills/pikku-permissions/SKILL.md +75 -229
  53. package/skills/pikku-react/SKILL.md +50 -298
  54. package/skills/pikku-react/references/client.md +313 -0
  55. package/skills/{pikku-react-query/SKILL.md → pikku-react/references/react-query.md} +2 -22
  56. package/skills/{pikku-workflows-client/SKILL.md → pikku-react/references/workflows.md} +1 -22
  57. package/skills/pikku-realtime/SKILL.md +110 -251
  58. package/skills/pikku-scenario/SKILL.md +60 -45
  59. package/skills/pikku-scenario/references/persona-run.md +148 -0
  60. package/skills/pikku-seo/SKILL.md +133 -0
  61. package/skills/pikku-service-backends/SKILL.md +154 -0
  62. package/skills/pikku-service-backends/references/aws.md +106 -0
  63. package/skills/pikku-service-backends/references/backblaze.md +57 -0
  64. package/skills/pikku-service-backends/references/mongodb.md +90 -0
  65. package/skills/pikku-service-backends/references/redis.md +75 -0
  66. package/skills/pikku-service-backends/references/schema.md +63 -0
  67. package/skills/pikku-services/SKILL.md +68 -291
  68. package/skills/{pikku-audit/SKILL.md → pikku-services/references/audit.md} +0 -22
  69. package/skills/{pikku-config/SKILL.md → pikku-services/references/config.md} +1 -25
  70. package/skills/{pikku-pino/SKILL.md → pikku-services/references/pino.md} +0 -20
  71. package/skills/pikku-services/references/services.md +272 -0
  72. package/skills/pikku-software-archaeology/README.md +5 -1
  73. package/skills/pikku-software-archaeology/SKILL.md +16 -2
  74. package/skills/{pikku-product-second-opinion/example/sample-report.md → pikku-software-archaeology/example/second-opinion-sample-report.md} +1 -1
  75. package/skills/pikku-software-archaeology/references/blueprint.schema.json +1 -1
  76. package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
  77. package/skills/{pikku-product-second-opinion/SKILL.md → pikku-software-archaeology/references/second-opinion.md} +4 -9
  78. package/skills/pikku-webhook/SKILL.md +224 -0
  79. package/skills/pikku-wiring/SKILL.md +180 -0
  80. package/skills/{pikku-websocket/SKILL.md → pikku-wiring/references/channel.md} +2 -35
  81. package/skills/{pikku-cli/SKILL.md → pikku-wiring/references/cli.md} +1 -33
  82. package/skills/{pikku-gateway-slack/SKILL.md → pikku-wiring/references/gateway-slack.md} +0 -23
  83. package/skills/{pikku-http/SKILL.md → pikku-wiring/references/http.md} +3 -39
  84. package/skills/{pikku-mcp/SKILL.md → pikku-wiring/references/mcp.md} +0 -33
  85. package/skills/{pikku-queue/SKILL.md → pikku-wiring/references/queue.md} +1 -33
  86. package/skills/pikku-wiring/references/realtime.md +265 -0
  87. package/skills/{pikku-rpc/SKILL.md → pikku-wiring/references/rpc.md} +0 -32
  88. package/skills/{pikku-schedule/SKILL.md → pikku-wiring/references/scheduler.md} +1 -35
  89. package/skills/{pikku-trigger/SKILL.md → pikku-wiring/references/trigger.md} +0 -43
  90. package/skills/pikku-workflow/SKILL.md +39 -2
  91. package/skills/pikku-aws/SKILL.md +0 -161
  92. package/skills/pikku-backblaze/SKILL.md +0 -104
  93. package/skills/pikku-deploy-cloudflare/SKILL.md +0 -123
  94. package/skills/pikku-deploy-express/SKILL.md +0 -122
  95. package/skills/pikku-deploy-uws/SKILL.md +0 -144
  96. package/skills/pikku-mongodb/SKILL.md +0 -113
  97. package/skills/pikku-product-second-opinion/README.md +0 -43
  98. package/skills/pikku-redis/SKILL.md +0 -99
  99. package/skills/pikku-schema-ajv/SKILL.md +0 -83
  100. package/skills/pikku-schema-cfworker/SKILL.md +0 -82
  101. package/skills/pikku-ws/SKILL.md +0 -87
  102. /package/skills/{pikku-build-app → pikku-build}/references/theming.md +0 -0
  103. /package/skills/{pikku-product-second-opinion/references/report-template.md → pikku-software-archaeology/references/second-opinion-report-template.md} +0 -0
  104. /package/skills/{pikku-cli/references/complete-example.md → pikku-wiring/references/cli-complete-example.md} +0 -0
  105. /package/skills/{pikku-http → pikku-wiring}/references/http-options.md +0 -0
  106. /package/skills/{pikku-realtime/references/other-routes.md → pikku-wiring/references/realtime-other-routes.md} +0 -0
@@ -0,0 +1,299 @@
1
+ # Pikku AI Agent Wiring
2
+
3
+
4
+ ## Before You Start
5
+
6
+ ```bash
7
+ pikku info functions --verbose # See existing functions that can be used as agent tools
8
+ pikku info tags --verbose # Understand project organization
9
+ ```
10
+
11
+ See `pikku-concepts` for the core mental model.
12
+
13
+ ## API Reference
14
+
15
+ ### `pikkuAgent(config)`
16
+
17
+ Import it from the generated agent types file — `#pikku` does not re-export it:
18
+
19
+ ```typescript
20
+ import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
21
+ import { ref } from '#pikku/function'
22
+
23
+ pikkuAgent({
24
+ name: string, // Unique agent identifier
25
+ description: string, // What the agent does (shown in agent listings)
26
+ summary?: string,
27
+ errors?: string[],
28
+
29
+ // --- system prompt: three fields, joined role → personality → goal ---
30
+ role?: string, // Who it is: 'You are a support engineer triaging bugs.'
31
+ personality?: string, // How it sounds: tone, verbosity
32
+ goal: string, // REQUIRED — what it is for
33
+
34
+ model: string, // e.g. 'openai/gpt-5-mini'
35
+ temperature?: number,
36
+ providerOptions?: { // passed through untouched, keyed by provider
37
+ openai?: { reasoningEffort?: 'minimal' | ... },
38
+ },
39
+
40
+ // --- capabilities: all three take ref() handles, not imported values ---
41
+ tools?: unknown[], // ref('todos:addTodo'), ref('graph:sleep'), …
42
+ agents?: unknown[], // sub-agents to delegate to
43
+ workflows?: unknown[], // workflows callable as a tool
44
+ agentMode?: 'delegate' | 'supervise',
45
+
46
+ memory?: {
47
+ storage?: string, // Service name for persistence (e.g. 'agentStorage')
48
+ vector?: string, // Vector store service name
49
+ embedder?: string, // Embedding service name
50
+ lastMessages?: number, // How many messages to retain in context
51
+ workingMemory?: ZodSchema, // Schema for structured working memory
52
+ },
53
+
54
+ maxSteps?: number, // Max tool-call rounds per invocation
55
+ toolChoice?: 'auto' | 'required' | 'none',
56
+ prepareStep?: (ctx) => void, // See "Narrowing tools per step"
57
+ input?: ZodSchema,
58
+ output?: ZodSchema, // Structured output — only honoured with NO tools
59
+ tags?: string[],
60
+
61
+ sessionScope?: 'user' | 'org', // Who owns this agent's threads. Default 'user'
62
+ auth?: boolean, // Default false — see below
63
+ scopes?: ScopeId[], // AND gate, checked before permissions
64
+ permissions?: PermissionGroup,
65
+
66
+ middleware?: PikkuMiddleware[],
67
+ channelMiddleware?: PikkuChannelMiddleware[],
68
+ agentMiddleware?: PikkuAgentMiddlewareHooks[],
69
+ })
70
+ ```
71
+
72
+ **`goal` is the required prompt field, not `instructions`** — there is no
73
+ `instructions` key. `role`/`personality`/`goal` are concatenated in that order,
74
+ and nothing validates which text lands in which, so the split is purely for
75
+ legibility: prose in the "wrong" one still reaches the model.
76
+
77
+ **Tools are `ref('domain:funcName')` handles, not imported function values.** The
78
+ inspector resolves the ref against the generated function map, which is what lets
79
+ an agent reach a function in another package (or a `graph:*` builtin) without an
80
+ import cycle.
81
+
82
+ `auth` defaults to `false` because agents are usually invoked from an
83
+ already-authenticated `pikkuFunc`. `scopes` and `permissions` are enforced either
84
+ way — see `pikku-auth`.
85
+
86
+ ### Invoking an agent
87
+
88
+ From inside a Pikku function, go through `wire.rpc.agent` — it carries the
89
+ session, credentials, and RPC depth for you:
90
+
91
+ ```typescript
92
+ const result = await rpc.agent.run('todo-agent', {
93
+ message, threadId, resourceId, // required
94
+ attachments?, model?, temperature?, context?,
95
+ })
96
+
97
+ await rpc.agent.stream('todo-agent', input) // writes to the wire's channel
98
+ await rpc.agent.approve(runId, [{ toolCallId, approved }], expectedAgentName?)
99
+ await rpc.agent.resume(runId, { toolCallId, approved })
100
+ await rpc.agent.interrupt(runId, 'user' | 'speech' | 'timeout')
101
+ ```
102
+
103
+ `context` is a string injected into the system prompt for this request only —
104
+ use it for upfront state (current org, project, deployment) so the agent stops
105
+ asking the user for identifiers it could have been handed.
106
+
107
+ `run` resolves to:
108
+
109
+ ```typescript
110
+ {
111
+ runId, threadId, text,
112
+ object?, // set when the agent has an `output` schema
113
+ steps, // tool calls made
114
+ usage: { inputTokens, outputTokens },
115
+ status?: 'completed' | 'suspended',
116
+ pendingApprovals?: [{ toolCallId, toolName, args, reason?, runId }],
117
+ }
118
+ ```
119
+
120
+ `runAgent` / `streamAgent` from `@pikku/core/agent` are the layer beneath
121
+ this. Their third argument is `RunAgentParams` (`{ sessionService?,
122
+ getCredential?, anonymousOwnerResourceId? }`) — **not** `{ singletonServices }`.
123
+ Reach for them only outside a wired function; inside one, `rpc.agent` is the
124
+ supported path.
125
+
126
+ ### Stream events
127
+
128
+ `rpc.agent.stream` pushes `AgentStreamEvent`s onto the channel:
129
+
130
+ ```typescript
131
+ // { type: 'step-start', stepNumber }
132
+ // { type: 'text-delta' | 'reasoning-delta', text }
133
+ // { type: 'tool-call', toolCallId, toolName, args }
134
+ // { type: 'tool-result', toolCallId, toolName, result }
135
+ // { type: 'agent-call' | 'agent-result', agentName, session, input | result }
136
+ // { type: 'approval-request', toolCallId, toolName, args, reason?, runId? }
137
+ // { type: 'credential-request', toolCallId, toolName, credentialName,
138
+ // credentialType: 'oauth2' | 'apikey', connectUrl?, runId }
139
+ // { type: 'usage', tokens: { input, output }, model }
140
+ // { type: 'transcript', text } // what the user was heard to say
141
+ // { type: 'audio-delta', data, format, text? } | { type: 'audio-done' }
142
+ // { type: 'data', name, data } | { type: 'generative-ui', spec }
143
+ // { type: 'suspended', reason: 'rpc-missing', missingRpcs }
144
+ // { type: 'interrupted', runId, text, reason }
145
+ // { type: 'error', message }
146
+ // { type: 'done' }
147
+ ```
148
+
149
+ Every event except `agent-call`/`agent-result`/`suspended` also carries optional
150
+ `agent` and `session` fields, so a UI can attribute output to a sub-agent rather
151
+ than folding it into the parent's transcript.
152
+
153
+ ## Usage Patterns
154
+
155
+ ### Define an Agent
156
+
157
+ ```typescript
158
+ import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
159
+ import { ref } from '#pikku/function'
160
+
161
+ export const todoAgent = pikkuAgent({
162
+ name: 'todo-agent',
163
+ description: 'Manages a todo list',
164
+ goal: 'You help users manage their todos. You can list, add, complete and delete them.',
165
+ model: 'openai/gpt-5-mini',
166
+ tools: [
167
+ ref('todos:listTodos'),
168
+ ref('todos:addTodo'),
169
+ ref('todos:completeTodo'),
170
+ ref('graph:sleep'),
171
+ ],
172
+ memory: { storage: 'agentStorage', lastMessages: 20 },
173
+ maxSteps: 10,
174
+ toolChoice: 'auto',
175
+ })
176
+ ```
177
+
178
+ ### Scaffold the HTTP surface
179
+
180
+ ```bash
181
+ pikku enable agent
182
+ ```
183
+
184
+ The next `pikku all` generates `agent.gen.ts` — run/stream/approve/resume
185
+ callers plus thread listing endpoints, with thread ownership already enforced
186
+ against the session. Don't hand-write these routes.
187
+
188
+ ### Structured output
189
+
190
+ An `output` schema fills `result.object`, but **only when the agent exposes no
191
+ tools** — with a tool present the runner falls back to free text, silently. If
192
+ you need both, split the classification into its own tool-free agent.
193
+
194
+ ```typescript
195
+ export const structuredAgent = pikkuAgent({
196
+ name: 'structured-agent',
197
+ description: 'Classifies a message and returns a structured verdict',
198
+ goal: 'You classify the sentiment of the user message.',
199
+ model: 'openai/gpt-5-mini',
200
+ output: z.object({ sentiment: z.string(), score: z.number() }),
201
+ })
202
+ ```
203
+
204
+ ### Narrowing tools per step
205
+
206
+ `prepareStep` runs before each step with the live tool array for that step, so
207
+ mutating it in place changes what the model is offered from there on. `stop()`
208
+ ends the loop — called before step 0 the run completes with an empty result
209
+ rather than signalling that it was short-circuited.
210
+
211
+ ```typescript
212
+ prepareStep: ({ stepNumber, tools }) => {
213
+ if (stepNumber >= 1) tools.length = 0 // withdraw tools after the first step
214
+ }
215
+ ```
216
+
217
+ ### Tool approval
218
+
219
+ A tool that should pause for a human sets `approvalRequired: true` (with an
220
+ optional `approvalDescription`) on the _function_, not on the agent. The run then
221
+ resolves with `status: 'suspended'` and `pendingApprovals`, and streaming emits
222
+ `approval-request`. Answer with `rpc.agent.approve(runId, approvals)`.
223
+
224
+ Authorization around tools is two-layer: an agent only sees tools its session can
225
+ reach, and the function's own `permissions` still guard the call when the model
226
+ picks one.
227
+
228
+ ### Thread ownership
229
+
230
+ `resourceId` is caller-supplied but never trusted as an owner. The session's
231
+ principal (`userId`, or `orgId` when `sessionScope: 'org'`) is prefixed onto it,
232
+ so a client can sub-partition inside its own boundary and cannot read across one.
233
+ A sessionless run gets an ephemeral anonymous owner instead.
234
+
235
+ ## Complete Example
236
+
237
+ ```typescript
238
+ // functions/todos.functions.ts
239
+ export const listTodos = pikkuSessionlessFunc({
240
+ description: 'List all todo items',
241
+ func: async ({ db }, { status }) => {
242
+ return { todos: await db.listTodos(status) }
243
+ },
244
+ })
245
+
246
+ export const createTodo = pikkuFunc({
247
+ description: 'Create a new todo item',
248
+ func: async ({ db }, { text, priority, dueDate }) => {
249
+ return await db.createTodo({ text, priority, dueDate })
250
+ },
251
+ })
252
+
253
+ export const completeTodo = pikkuFunc({
254
+ description: 'Mark a todo as complete',
255
+ func: async ({ db }, { todoId }) => {
256
+ return await db.completeTodo(todoId)
257
+ },
258
+ })
259
+
260
+ // agents/todo-assistant.agent.ts
261
+ import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
262
+ import { ref } from '#pikku/function'
263
+
264
+ export const todoAssistant = pikkuAgent({
265
+ name: 'todo-assistant',
266
+ description: 'A helpful assistant that manages todos',
267
+ role: 'You are an assistant that manages a user’s todo list.',
268
+ personality: 'Concise. One short paragraph unless asked for detail.',
269
+ goal: `Keep the user's todos accurate.
270
+ - When creating todos, infer priority if not specified
271
+ - When listing todos, summarize the results`,
272
+ model: 'openai/gpt-5-mini',
273
+ tools: [
274
+ ref('todos:listTodos'),
275
+ ref('todos:createTodo'),
276
+ ref('todos:completeTodo'),
277
+ ],
278
+ memory: { storage: 'agentStorage', lastMessages: 20 },
279
+ maxSteps: 5,
280
+ temperature: 0.7,
281
+ })
282
+
283
+ // Wire to HTTP for a chat endpoint — or skip this entirely and run
284
+ // `pikku enable agent`, which scaffolds run/stream/approve/resume for you.
285
+ wireHTTP({
286
+ method: 'post',
287
+ route: '/chat',
288
+ func: pikkuFunc({
289
+ title: 'Chat',
290
+ func: async (_services, { message, threadId }, { session, rpc }) => {
291
+ return await rpc.agent.run('todo-assistant', {
292
+ message,
293
+ threadId,
294
+ resourceId: session.userId,
295
+ })
296
+ },
297
+ }),
298
+ })
299
+ ```
@@ -1,27 +1,5 @@
1
- ---
2
- name: pikku-ai-vercel
3
- description: >-
4
- Use when setting up AI agent execution with the Vercel AI SDK in a Pikku app. Covers
5
- VercelAgentRunner for streaming and non-streaming AI agent steps. TRIGGER when: code uses
6
- VercelAgentRunner, user asks about Vercel AI SDK integration, AI agent runners, or
7
- @pikku/ai-vercel. DO NOT TRIGGER when: user asks about AI agent wiring (use pikku-agent) or
8
- voice I/O (use pikku-ai-voice).
9
- installGroups: [fabric]
10
- ---
11
-
12
1
  # Pikku AI Vercel (Agent Runner)
13
2
 
14
- ## Agent Operating Procedure
15
-
16
- Use this skill as an execution checklist, not reference material.
17
-
18
- 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
19
- 2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
20
- 3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
21
- 4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
22
- 5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
23
-
24
- `@pikku/ai-vercel` provides an AI agent runner backed by the [Vercel AI SDK](https://sdk.vercel.ai/). Implements `AgentRunnerService` from `@pikku/core`.
25
3
 
26
4
  ## Installation
27
5
 
@@ -47,7 +25,7 @@ const runner = new VercelAgentRunner(
47
25
 
48
26
  - `stream(params: AgentRunnerParams, channel: AgentStreamChannel): Promise<AgentStepResult>` — Stream AI responses with tool calls
49
27
  - `run(params: AgentRunnerParams): Promise<AgentStepResult>` — Execute a single AI step (non-streaming)
50
- - `transcribe({ model, audio, … })` / `generateSpeech({ model, text, voice, … })` — what `voiceInput`/`voiceOutput` call; see `pikku-ai-voice`
28
+ - `transcribe({ model, audio, … })` / `generateSpeech({ model, text, voice, … })` — what `voiceInput`/`voiceOutput` call; see `references/voice.md`
51
29
  - `generateImage`, `embed`, `embedMany`, `rerank` — the remaining AI SDK surfaces
52
30
  - `withApiKey(apiKey)` — returns a **new** runner built from `providerFactory`; returns `this` unchanged when no factory was supplied or the key is blank. This is the per-user-credential path
53
31
 
@@ -107,7 +85,7 @@ export const assistant = pikkuAgent({
107
85
  ```
108
86
 
109
87
  There is no `wireAgent` — agents are declared with `pikkuAgent` from the
110
- generated agent types. See `pikku-agent` for the full config.
88
+ generated agent types. See `references/agents.md` for the full config.
111
89
 
112
90
  ### Testing without a real provider
113
91
 
@@ -1,26 +1,5 @@
1
- ---
2
- name: pikku-ai-voice
3
- description: >-
4
- Use when adding voice input (speech-to-text) or voice output (text-to-speech) to AI agents in a
5
- Pikku app. Covers the voiceInput/voiceOutput AI middleware from @pikku/core/agent, per-script
6
- voices, and barge-in. TRIGGER when: code uses voiceInput, voiceOutput, or user asks about voice
7
- agents, speech-to-text, text-to-speech, transcription, or @pikku/ai-voice. DO NOT TRIGGER when:
8
- user asks about AI agent wiring generally (use pikku-agent) or the runner itself (use
9
- pikku-ai-vercel).
10
- installGroups: [fabric]
11
- ---
12
-
13
1
  # Pikku AI Voice (Speech I/O)
14
2
 
15
- ## Agent Operating Procedure
16
-
17
- Use this skill as an execution checklist, not reference material.
18
-
19
- 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
20
- 2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
21
- 3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
22
- 4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
23
- 5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
24
3
 
25
4
  ## `@pikku/ai-voice` is deprecated and empty
26
5
 
@@ -30,7 +9,7 @@ dependency.
30
9
 
31
10
  Voice now lives in **`@pikku/core/agent`** as two AI middlewares, and the
32
11
  speech models are reached through the `agentRunner` (`transcribe` /
33
- `generateSpeech`) rather than through separate services. See `pikku-ai-vercel`.
12
+ `generateSpeech`) rather than through separate services. See `references/runner-vercel.md`.
34
13
 
35
14
  ## API Reference
36
15
 
@@ -0,0 +1,265 @@
1
+ ---
2
+ name: pikku-architect
3
+ description: >-
4
+ Use to turn one settled milestone note into the technical plan the build is measured against —
5
+ the tables, functions, wires, roles, scopes, screens and scenarios it owes, split into passes and
6
+ written through `pikku knowledge plan set`. This is a SEPARATE SEAT from the build: the plan is
7
+ the denominator `pikku knowledge plan progress` divides by, so whoever writes it must not be the
8
+ one grading themselves against it. TRIGGER when: a milestone note is settled and the next step is
9
+ planning it, the user asks to plan or architect a milestone, `pikku knowledge plan progress` says
10
+ a milestone has no plan, or pikku-build's App mode reaches a milestone with nothing planned. DO
11
+ NOT TRIGGER when: the milestone notes themselves are still being written (use pikku-knowledge),
12
+ the plan already exists and the job is to build it (use pikku-build), or the ask is a one-off
13
+ edit to a working app.
14
+ installGroups: [core]
15
+ ---
16
+
17
+ # Plan one milestone
18
+
19
+ A milestone note says what the app must DO and how it must feel for the person using it. It
20
+ deliberately does not say how. You are the seat that decides how, once, in writing, before anyone
21
+ builds it.
22
+
23
+ **Why this is a separate seat.** The build agent used to write its own plan. That makes one party
24
+ both author and examiner: it can build a fraction, plan only that fraction, and certify itself
25
+ complete — and `pikku knowledge plan progress` then divides by a denominator the builder chose
26
+ after seeing its own answer. A plan written here, against the note, by someone who is not going to
27
+ build it, is the denominator the builder does not own.
28
+
29
+ **One milestone, one plan, then stop.** Do not build in this session. Do not plan the next
30
+ milestone "while you are here" — the notes after this one are still allowed to change, and a plan
31
+ written against a note that later moves is worse than no plan.
32
+
33
+ ---
34
+
35
+ ## Write nothing by hand
36
+
37
+ The plan reaches disk through `pikku knowledge plan set <milestone> <file>` and nowhere else. It
38
+ validates first and names the field that is wrong if it refuses; a plan file written with an editor
39
+ is a plan nothing checked, and the place that discovers that is a finished build.
40
+
41
+ It is JSON rather than a note on purpose. Everything else under `knowledge/` is prose a human
42
+ reads; this one is consumed field-by-field, and a markdown parser is one more place a misspelt
43
+ heading silently passes. It cannot live INSIDE the milestone note either: that note is frozen once
44
+ its status leaves `proposed`, so rewriting it would change what the builder was told.
45
+
46
+ ## Send it. Do not go looking.
47
+
48
+ ```sh
49
+ pikku knowledge plan schema
50
+ ```
51
+
52
+ That is the specification, in full, with every field's guidance in its `description`. There is no
53
+ second plan-format doc. So when you are unsure what a field wants, **write your best honest reading
54
+ and send it** — `plan set` validates every field and names the exact one that is wrong, so a wrong
55
+ guess costs one round trip and teaches you the answer.
56
+
57
+ The failure mode to recognise in yourself: you have decided the tables, the passes and the
58
+ functions, and you are still reading. That is the moment to run `plan set`.
59
+
60
+ ---
61
+
62
+ ## The turn
63
+
64
+ ### 1. Read what has been settled
65
+
66
+ ```sh
67
+ pikku knowledge validate # the base is consistent before you plan against it
68
+ pikku meta context --json # what the app already declares
69
+ pikku knowledge plan schema # the only spec for what you are about to write
70
+ ```
71
+
72
+ Then read, in the tree: the milestone's own note in full, every note it names on `entities:` and
73
+ `requires:`, the decisions that constrain it, and the migrations already in `db/sqlite/` — those
74
+ say whether your tables are new or an alter.
75
+
76
+ **Do not re-interview.** If the note leaves something genuinely undecided, plan the reading that
77
+ builds LESS. A smaller milestone that ships is worth more than a complete one that does not, and
78
+ what you leave out is named in `covers` for the next milestone to pick up.
79
+
80
+ ### 2. Decide the passes
81
+
82
+ A pass is a slice of the milestone that stands up on its own. **Pass 1 is a walking skeleton**: it
83
+ reaches a real screen, with real functions behind it, proved by a real browser scenario. Everything
84
+ else waits behind it.
85
+
86
+ This is enforced, not advisory — `plan set` refuses a plan whose pass 1 has no `ui` item, no
87
+ `functions` item, or a pass-1 route with nothing proving it works. The reason is the failure it was
88
+ written against: a milestone that built four unwired functions and no page, and reported itself
89
+ finished. A build that runs out of time in pass 2 has shipped something; one that runs out of time
90
+ having built pass 1 across four half-finished layers has shipped nothing.
91
+
92
+ **Only pass 1 blocks.** `pikku knowledge plan progress` reports a later pass under `deferred` and
93
+ never refuses on it. That is what stops plan size from being fatal — but it is not licence to plan
94
+ a milestone nobody could finish. The question that decides a plan's size is not "what does this
95
+ note imply" but **"could a build finish all of this if pass 1 took twice as long as I expect"** — if
96
+ not, it is two milestones. Plan the first, and say in `covers` what you left behind.
97
+
98
+ **A screen is what pass 1 reaches only when the milestone IS an app.** The note's `surface:` says
99
+ which it is — absent means an app, and `cli`, `mcp`, `agent` and `backend` are the others. On those,
100
+ `ui` is legitimately `n/a` (with its reason, like any slot), and pass 1 proves itself one level
101
+ down: a pass-1 function that is actually wired, and a `scenarios.backend` item carrying that
102
+ function's name in its `fn` field. The obligation never lifts, it only moves — read the surface off
103
+ the note before you decide the passes.
104
+
105
+ ### 3. Say what each slot is, or say why it is nothing
106
+
107
+ Every slot — `model`, `functions`, `roles`, `scopes`, `ui`, and each level of `scenarios` — is
108
+ either `{"kind": "built", "description": ..., "items": [...]}` or `{"kind": "n/a", "description":
109
+ ...}`. **Both carry prose.**
110
+
111
+ There is no way to leave a slot out, and that is the point: "no roles, because everyone using this
112
+ app is the same kind of person" and "nobody thought about roles" must not look alike. Write the
113
+ `n/a` reason as a sentence a reader would accept, not as the word "none".
114
+
115
+ ### 4. Write it
116
+
117
+ ```sh
118
+ pikku knowledge plan set <milestone> /tmp/plan.json
119
+ ```
120
+
121
+ Write the JSON to a file first — the command takes a path, not inline JSON, which is what keeps an
122
+ apostrophe in a `description` from ending a shell argument. If it is refused, the refusal names the
123
+ field path. Fix that field and send it again; do not restructure the plan around a refusal you have
124
+ not read.
125
+
126
+ Then confirm what the builder will be handed:
127
+
128
+ ```sh
129
+ pikku knowledge plan show <milestone> --for-build
130
+ ```
131
+
132
+ ---
133
+
134
+ ## What the plan holds
135
+
136
+ The plan holds INTENT. Reality lives in pikku's generated meta under `.pikku/`, which already
137
+ inventories every function, wire, scope, role, workflow, agent and scenario. Nothing here
138
+ duplicates that — only what codegen cannot infer: **why a thing exists, which pass it belongs to,
139
+ and which knowledge note it discharges.**
140
+
141
+ ### `covers` — which notes this milestone discharges
142
+
143
+ Every plan claims at least one knowledge note: `note` (its path under `knowledge/`), `hash` (what
144
+ that note's body hashes to right now) and `complete`.
145
+
146
+ `complete: false` is the honest answer for a note whose claims span several milestones — claim the
147
+ whole of a note only when this milestone genuinely leaves nothing of it unbuilt, because a note
148
+ marked complete is a note nobody looks at again.
149
+
150
+ **You do not have to compute the hash.** Write anything twelve characters long and send the plan:
151
+ `plan set` refuses a hash that is not the note's current one and names the correct one, so one
152
+ round trip gets you every hash in the plan. That refusal is the point of the field — a hash that
153
+ was never right makes the note read as edited-since from the moment the milestone ships, and it
154
+ drops back into a backlog nobody planned.
155
+
156
+ ### `model` — tables, and what their columns HOLD
157
+
158
+ Each field carries a `classification`: `public`, `internal`, `personal` or `sensitive`. That is what
159
+ lets a permission claim be checked against the data rather than only against itself — a function
160
+ returning a `personal` column with no permission rule is a defect the gate can name. It is also what
161
+ `db/annotations.ts` ends up expressing, so plan it here rather than discovering it at migrate time.
162
+
163
+ Each relationship carries `onDelete`: `cascade`, `restrict` or `orphan`. A foreign key states which
164
+ rows are related; it does not state what the product wants when the parent goes, and those three
165
+ produce identical schemas until someone deletes something. A `cascade` is checked against the
166
+ migrations by `plan progress`, and needs `provedBy` naming a scenario in this same plan that deletes
167
+ the parent and asserts the children are gone.
168
+
169
+ A table that already exists is altered by a NEW forward migration, numbered on from the ones in
170
+ `db/sqlite/`. Editing an applied migration is the hash mismatch that makes a deployed database
171
+ refuse to migrate, so plan the alter as its own file.
172
+
173
+ ### `functions` — with their wire and their rule on them
174
+
175
+ The wire and the permission live ON the function, because that is where pikku enforces them. Two
176
+ parallel lists are two lists that drift.
177
+
178
+ **Do not give a function a `wire`.** pikku already serves every `expose: true` function as an RPC
179
+ and the client calls it by name, so for nearly every function there is nothing to decide — leave the
180
+ field out. A `wire` is for the exceptions: its own HTTP path via `wireHTTP` (a webhook, a payment
181
+ callback, a public URL another system posts to), a queue job, a channel, a scheduled task, or a
182
+ workflow entry point. Those last two are not alternate URLs — they are what the milestone IS, and a
183
+ plan that omits them ships a `status` column nothing advances or a job nobody runs.
184
+
185
+ `permission` is a SENTENCE, not a role name — "only the person who wrote it can edit it". The roles
186
+ are the engineer's choice; the rule is the part that has to survive being implemented, in the
187
+ function's `permissions` field and never in its body. `null` means open to anyone signed in, and
188
+ stating that is different from omitting it. **Every function with a permission rule needs a
189
+ permission scenario naming it in `fn`** — a rule with no failing case is a claim, not a check, and
190
+ `plan set` refuses the plan without one.
191
+
192
+ ### `scopes` — what a KIND of user may do, never who owns a row
193
+
194
+ A scope depends ONLY on the session: "may this kind of user do this at all" — `admin:invoices:void`,
195
+ `billing`. It is declared with `wireScope` and granted in `mapSession`, so every name here has to
196
+ end up in pikku's generated scope meta. One that cannot be declared is one the build can never
197
+ finish, and `plan progress` refuses the milestone for as long as it stands.
198
+
199
+ Ownership is not a scope. "Only the owner of the house may read it" depends on the row being asked
200
+ for, and a scope never sees the row — that is the function's `permission` sentence and lives nowhere
201
+ else. If the rule mentions the record, it is a `permission`; if it reads the same for every row that
202
+ user touches, it is a scope. An app built from one person's idea usually has none at all, so
203
+ `{"kind": "n/a", ...}` is the ordinary answer here.
204
+
205
+ ### `roles` — and the app each one signs into
206
+
207
+ The distinct `app` values across `roles` ARE the frontends this project gets, and nothing downstream
208
+ can recover the answer. Colleagues share ONE app and differ by nav and permitted actions (the
209
+ mechanic, the person on the counter, the bookkeeper); someone across the counter with an account
210
+ gets their own (the customer, the tenant, the patient). One app is a real answer and often the right
211
+ one — then every role carries the same slug. Never invent a person the notes do not name in order to
212
+ reach two, and never give a slug to someone who never signs in: a guest checking out takes the same
213
+ slug as the seller they buy from, on that app's public routes outside `/app`. Once there is more
214
+ than one app, every `ui` item carries its `app` too.
215
+
216
+ Adding the second frontend is the BUILD's job, at the milestone that first needs it —
217
+ pikku-build's multi-app reference. Your part is recording which app each person is in.
218
+
219
+ ### `ui` — routes, and what is on them
220
+
221
+ One item per route, each with the pass that builds it. A pass-1 route has to be LINKED to the
222
+ scenario that proves it, and there are two ways: name the scenario in that `ui` item's own
223
+ `scenarios` array, or write a browser scenario whose `feature` contains the route path. Nothing else
224
+ counts — an unlinked browser scenario reads as a route nobody proved, and the plan is refused.
225
+
226
+ ### `scenarios` — keyed by level
227
+
228
+ `backend`, `browser`, `permission`, each its own slot. Keyed rather than tagged so that a plan with
229
+ four backend scenarios and no browser scenario fails on its SHAPE — a flat list lets that through,
230
+ and that is exactly the milestone that builds an API and ships no screen.
231
+
232
+ **Every scenario needs `name`: the `pikkuScenario` export it becomes** (`saveEntryScenario`).
233
+ `feature` and `scenario` are prose for a reader, and prose cannot be matched against codegen.
234
+ `plan progress` looks for the export by that exact name, so a scenario with no name is one the gate
235
+ cannot see.
236
+
237
+ Permission scenarios default to pass 2 — they harden a journey that has to exist before they can
238
+ cover it — so a role × resource cross product there costs the milestone nothing.
239
+
240
+ ---
241
+
242
+ ## What makes a plan wrong
243
+
244
+ `plan set` catches the mechanical failures. These are the ones it cannot:
245
+
246
+ - **A plan for a different milestone.** The note is about `entries`; the plan builds `projects`.
247
+ Every entity the note names must appear in a function or a table.
248
+ - **A pass 1 that is a layer, not a slice.** "Pass 1: the data model. Pass 2: the API. Pass 3: the
249
+ screens." That is three passes of nothing working.
250
+ - **Scenarios that assert the code ran rather than that the person got what they came for.** A
251
+ scenario proving `saveEntry` returns 200 proves the wire. The one worth planning is the one where
252
+ a person writes something, comes back, and it is still there. A browser scenario that opens a page
253
+ and asserts it is still on it proves the route loads and nothing else —
254
+ `pikku knowledge plan progress` names it as a problem and refuses the milestone.
255
+ - **A permission rule invented here.** If the notes do not say who may do a thing, the answer is
256
+ `null` with the reason, not a rule you made up. A rule the user never agreed to is one they find
257
+ out about by being locked out of their own app.
258
+
259
+ ---
260
+
261
+ ## When you are done
262
+
263
+ The accepted `plan set` is the end of the seat. Hand the milestone to `pikku-build`, which reads the
264
+ plan with `plan show --for-build`, builds it, and closes the milestone only when
265
+ `pikku knowledge plan progress` is clean. What you wrote is what it is measured against.