@pikku/skills 0.12.22 → 0.12.25

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 (101) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/dist/skills.gen.js +1 -1
  3. package/package.json +1 -1
  4. package/skills/pikku-addon/SKILL.md +2 -2
  5. package/skills/pikku-agent/SKILL.md +67 -316
  6. package/skills/pikku-agent/references/agents.md +299 -0
  7. package/skills/{pikku-ai-vercel/SKILL.md → pikku-agent/references/runner-vercel.md} +2 -24
  8. package/skills/{pikku-ai-voice/SKILL.md → pikku-agent/references/voice.md} +1 -22
  9. package/skills/pikku-architect/SKILL.md +264 -0
  10. package/skills/pikku-auth/SKILL.md +89 -0
  11. package/skills/{pikku-better-auth/SKILL.md → pikku-auth/references/better-auth.md} +1 -27
  12. package/skills/{pikku-jose/SKILL.md → pikku-auth/references/jose.md} +1 -23
  13. package/skills/{pikku-machine-auth/SKILL.md → pikku-auth/references/machine-auth.md} +0 -23
  14. package/skills/{pikku-permissions/SKILL.md → pikku-auth/references/permissions.md} +1 -20
  15. package/skills/{pikku-security/SKILL.md → pikku-auth/references/sessions.md} +0 -20
  16. package/skills/pikku-build/SKILL.md +87 -0
  17. package/skills/{pikku-build-app/SKILL.md → pikku-build/references/app.md} +75 -23
  18. package/skills/{pikku-feature/SKILL.md → pikku-build/references/feature.md} +1 -8
  19. package/skills/{pikku-build-app → pikku-build}/references/multi-app.md +1 -1
  20. package/skills/{pikku-build-platform/SKILL.md → pikku-build/references/platform.md} +22 -37
  21. package/skills/{pikku-template-clone/SKILL.md → pikku-build/references/post-clone.md} +0 -6
  22. package/skills/{pikku-build-quick/SKILL.md → pikku-build/references/quick.md} +4 -20
  23. package/skills/{pikku-build-app → pikku-build}/references/ship.md +7 -1
  24. package/skills/pikku-concepts/SKILL.md +72 -7
  25. package/skills/pikku-concepts/references/concept-mapping.md +8 -8
  26. package/skills/pikku-deploy/SKILL.md +158 -0
  27. package/skills/{pikku-deploy-azure/SKILL.md → pikku-deploy/references/azure.md} +18 -50
  28. package/skills/pikku-deploy/references/cloudflare.md +104 -0
  29. package/skills/pikku-deploy/references/express.md +92 -0
  30. package/skills/{pikku-deploy-fastify/SKILL.md → pikku-deploy/references/fastify.md} +8 -32
  31. package/skills/{pikku-deploy-lambda/SKILL.md → pikku-deploy/references/lambda.md} +6 -27
  32. package/skills/{pikku-deploy-nextjs/SKILL.md → pikku-deploy/references/nextjs.md} +9 -33
  33. package/skills/pikku-deploy/references/uws.md +72 -0
  34. package/skills/pikku-deploy/references/ws.md +75 -0
  35. package/skills/pikku-emails/SKILL.md +3 -2
  36. package/skills/pikku-fabric/SKILL.md +12 -2
  37. package/skills/{pikku-fabric-debug/SKILL.md → pikku-fabric/references/debugging.md} +0 -6
  38. package/skills/pikku-i18n/SKILL.md +60 -207
  39. package/skills/{pikku-paraglide/SKILL.md → pikku-i18n/references/enum-labels.md} +0 -6
  40. package/skills/pikku-i18n/references/messages.md +218 -0
  41. package/skills/{pikku-rtl/SKILL.md → pikku-i18n/references/rtl.md} +3 -9
  42. package/skills/pikku-knowledge/SKILL.md +14 -0
  43. package/skills/pikku-kysely/SKILL.md +13 -13
  44. package/skills/pikku-meta/SKILL.md +58 -130
  45. package/skills/{pikku-deps/SKILL.md → pikku-meta/references/audit.md} +1 -17
  46. package/skills/pikku-meta/references/meta.md +114 -0
  47. package/skills/{pikku-versioning/SKILL.md → pikku-meta/references/versioning.md} +0 -26
  48. package/skills/pikku-middleware/SKILL.md +5 -5
  49. package/skills/pikku-n8n-import/SKILL.md +0 -1
  50. package/skills/pikku-react/SKILL.md +50 -298
  51. package/skills/pikku-react/references/client.md +293 -0
  52. package/skills/{pikku-react-query/SKILL.md → pikku-react/references/react-query.md} +2 -22
  53. package/skills/{pikku-workflows-client/SKILL.md → pikku-react/references/workflows.md} +1 -22
  54. package/skills/pikku-scenario/SKILL.md +60 -45
  55. package/skills/pikku-scenario/references/persona-run.md +148 -0
  56. package/skills/pikku-service-backends/SKILL.md +154 -0
  57. package/skills/pikku-service-backends/references/aws.md +106 -0
  58. package/skills/pikku-service-backends/references/backblaze.md +57 -0
  59. package/skills/pikku-service-backends/references/mongodb.md +90 -0
  60. package/skills/pikku-service-backends/references/redis.md +75 -0
  61. package/skills/pikku-service-backends/references/schema.md +63 -0
  62. package/skills/pikku-services/SKILL.md +68 -291
  63. package/skills/{pikku-audit/SKILL.md → pikku-services/references/audit.md} +0 -22
  64. package/skills/{pikku-config/SKILL.md → pikku-services/references/config.md} +1 -25
  65. package/skills/{pikku-pino/SKILL.md → pikku-services/references/pino.md} +0 -20
  66. package/skills/pikku-services/references/services.md +272 -0
  67. package/skills/pikku-software-archaeology/README.md +5 -1
  68. package/skills/pikku-software-archaeology/SKILL.md +15 -2
  69. package/skills/{pikku-product-second-opinion/example/sample-report.md → pikku-software-archaeology/example/second-opinion-sample-report.md} +1 -1
  70. package/skills/pikku-software-archaeology/references/blueprint.schema.json +1 -1
  71. package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
  72. package/skills/{pikku-product-second-opinion/SKILL.md → pikku-software-archaeology/references/second-opinion.md} +4 -9
  73. package/skills/pikku-webhook/SKILL.md +199 -0
  74. package/skills/pikku-wiring/SKILL.md +180 -0
  75. package/skills/{pikku-websocket/SKILL.md → pikku-wiring/references/channel.md} +2 -35
  76. package/skills/{pikku-cli/SKILL.md → pikku-wiring/references/cli.md} +1 -33
  77. package/skills/{pikku-gateway-slack/SKILL.md → pikku-wiring/references/gateway-slack.md} +0 -23
  78. package/skills/{pikku-http/SKILL.md → pikku-wiring/references/http.md} +3 -39
  79. package/skills/{pikku-mcp/SKILL.md → pikku-wiring/references/mcp.md} +0 -33
  80. package/skills/{pikku-queue/SKILL.md → pikku-wiring/references/queue.md} +1 -33
  81. package/skills/{pikku-realtime/SKILL.md → pikku-wiring/references/realtime.md} +2 -25
  82. package/skills/{pikku-rpc/SKILL.md → pikku-wiring/references/rpc.md} +0 -32
  83. package/skills/{pikku-schedule/SKILL.md → pikku-wiring/references/scheduler.md} +1 -35
  84. package/skills/{pikku-trigger/SKILL.md → pikku-wiring/references/trigger.md} +0 -43
  85. package/skills/pikku-workflow/SKILL.md +2 -2
  86. package/skills/pikku-aws/SKILL.md +0 -161
  87. package/skills/pikku-backblaze/SKILL.md +0 -104
  88. package/skills/pikku-deploy-cloudflare/SKILL.md +0 -123
  89. package/skills/pikku-deploy-express/SKILL.md +0 -122
  90. package/skills/pikku-deploy-uws/SKILL.md +0 -144
  91. package/skills/pikku-mongodb/SKILL.md +0 -113
  92. package/skills/pikku-product-second-opinion/README.md +0 -43
  93. package/skills/pikku-redis/SKILL.md +0 -99
  94. package/skills/pikku-schema-ajv/SKILL.md +0 -83
  95. package/skills/pikku-schema-cfworker/SKILL.md +0 -82
  96. package/skills/pikku-ws/SKILL.md +0 -87
  97. /package/skills/{pikku-build-app → pikku-build}/references/theming.md +0 -0
  98. /package/skills/{pikku-product-second-opinion/references/report-template.md → pikku-software-archaeology/references/second-opinion-report-template.md} +0 -0
  99. /package/skills/{pikku-cli/references/complete-example.md → pikku-wiring/references/cli-complete-example.md} +0 -0
  100. /package/skills/{pikku-http → pikku-wiring}/references/http-options.md +0 -0
  101. /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,264 @@
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
+ ---
15
+
16
+ # Plan one milestone
17
+
18
+ A milestone note says what the app must DO and how it must feel for the person using it. It
19
+ deliberately does not say how. You are the seat that decides how, once, in writing, before anyone
20
+ builds it.
21
+
22
+ **Why this is a separate seat.** The build agent used to write its own plan. That makes one party
23
+ both author and examiner: it can build a fraction, plan only that fraction, and certify itself
24
+ complete — and `pikku knowledge plan progress` then divides by a denominator the builder chose
25
+ after seeing its own answer. A plan written here, against the note, by someone who is not going to
26
+ build it, is the denominator the builder does not own.
27
+
28
+ **One milestone, one plan, then stop.** Do not build in this session. Do not plan the next
29
+ milestone "while you are here" — the notes after this one are still allowed to change, and a plan
30
+ written against a note that later moves is worse than no plan.
31
+
32
+ ---
33
+
34
+ ## Write nothing by hand
35
+
36
+ The plan reaches disk through `pikku knowledge plan set <milestone> <file>` and nowhere else. It
37
+ validates first and names the field that is wrong if it refuses; a plan file written with an editor
38
+ is a plan nothing checked, and the place that discovers that is a finished build.
39
+
40
+ It is JSON rather than a note on purpose. Everything else under `knowledge/` is prose a human
41
+ reads; this one is consumed field-by-field, and a markdown parser is one more place a misspelt
42
+ heading silently passes. It cannot live INSIDE the milestone note either: that note is frozen once
43
+ its status leaves `proposed`, so rewriting it would change what the builder was told.
44
+
45
+ ## Send it. Do not go looking.
46
+
47
+ ```sh
48
+ pikku knowledge plan schema
49
+ ```
50
+
51
+ That is the specification, in full, with every field's guidance in its `description`. There is no
52
+ second plan-format doc. So when you are unsure what a field wants, **write your best honest reading
53
+ and send it** — `plan set` validates every field and names the exact one that is wrong, so a wrong
54
+ guess costs one round trip and teaches you the answer.
55
+
56
+ The failure mode to recognise in yourself: you have decided the tables, the passes and the
57
+ functions, and you are still reading. That is the moment to run `plan set`.
58
+
59
+ ---
60
+
61
+ ## The turn
62
+
63
+ ### 1. Read what has been settled
64
+
65
+ ```sh
66
+ pikku knowledge validate # the base is consistent before you plan against it
67
+ pikku meta context --json # what the app already declares
68
+ pikku knowledge plan schema # the only spec for what you are about to write
69
+ ```
70
+
71
+ Then read, in the tree: the milestone's own note in full, every note it names on `entities:` and
72
+ `requires:`, the decisions that constrain it, and the migrations already in `db/sqlite/` — those
73
+ say whether your tables are new or an alter.
74
+
75
+ **Do not re-interview.** If the note leaves something genuinely undecided, plan the reading that
76
+ builds LESS. A smaller milestone that ships is worth more than a complete one that does not, and
77
+ what you leave out is named in `covers` for the next milestone to pick up.
78
+
79
+ ### 2. Decide the passes
80
+
81
+ A pass is a slice of the milestone that stands up on its own. **Pass 1 is a walking skeleton**: it
82
+ reaches a real screen, with real functions behind it, proved by a real browser scenario. Everything
83
+ else waits behind it.
84
+
85
+ This is enforced, not advisory — `plan set` refuses a plan whose pass 1 has no `ui` item, no
86
+ `functions` item, or a pass-1 route with nothing proving it works. The reason is the failure it was
87
+ written against: a milestone that built four unwired functions and no page, and reported itself
88
+ finished. A build that runs out of time in pass 2 has shipped something; one that runs out of time
89
+ having built pass 1 across four half-finished layers has shipped nothing.
90
+
91
+ **Only pass 1 blocks.** `pikku knowledge plan progress` reports a later pass under `deferred` and
92
+ never refuses on it. That is what stops plan size from being fatal — but it is not licence to plan
93
+ a milestone nobody could finish. The question that decides a plan's size is not "what does this
94
+ note imply" but **"could a build finish all of this if pass 1 took twice as long as I expect"** — if
95
+ not, it is two milestones. Plan the first, and say in `covers` what you left behind.
96
+
97
+ **A screen is what pass 1 reaches only when the milestone IS an app.** The note's `surface:` says
98
+ which it is — absent means an app, and `cli`, `mcp`, `agent` and `backend` are the others. On those,
99
+ `ui` is legitimately `n/a` (with its reason, like any slot), and pass 1 proves itself one level
100
+ down: a pass-1 function that is actually wired, and a `scenarios.backend` item carrying that
101
+ function's name in its `fn` field. The obligation never lifts, it only moves — read the surface off
102
+ the note before you decide the passes.
103
+
104
+ ### 3. Say what each slot is, or say why it is nothing
105
+
106
+ Every slot — `model`, `functions`, `roles`, `scopes`, `ui`, and each level of `scenarios` — is
107
+ either `{"kind": "built", "description": ..., "items": [...]}` or `{"kind": "n/a", "description":
108
+ ...}`. **Both carry prose.**
109
+
110
+ There is no way to leave a slot out, and that is the point: "no roles, because everyone using this
111
+ app is the same kind of person" and "nobody thought about roles" must not look alike. Write the
112
+ `n/a` reason as a sentence a reader would accept, not as the word "none".
113
+
114
+ ### 4. Write it
115
+
116
+ ```sh
117
+ pikku knowledge plan set <milestone> /tmp/plan.json
118
+ ```
119
+
120
+ Write the JSON to a file first — the command takes a path, not inline JSON, which is what keeps an
121
+ apostrophe in a `description` from ending a shell argument. If it is refused, the refusal names the
122
+ field path. Fix that field and send it again; do not restructure the plan around a refusal you have
123
+ not read.
124
+
125
+ Then confirm what the builder will be handed:
126
+
127
+ ```sh
128
+ pikku knowledge plan show <milestone> --for-build
129
+ ```
130
+
131
+ ---
132
+
133
+ ## What the plan holds
134
+
135
+ The plan holds INTENT. Reality lives in pikku's generated meta under `.pikku/`, which already
136
+ inventories every function, wire, scope, role, workflow, agent and scenario. Nothing here
137
+ duplicates that — only what codegen cannot infer: **why a thing exists, which pass it belongs to,
138
+ and which knowledge note it discharges.**
139
+
140
+ ### `covers` — which notes this milestone discharges
141
+
142
+ Every plan claims at least one knowledge note: `note` (its path under `knowledge/`), `hash` (what
143
+ that note's body hashes to right now) and `complete`.
144
+
145
+ `complete: false` is the honest answer for a note whose claims span several milestones — claim the
146
+ whole of a note only when this milestone genuinely leaves nothing of it unbuilt, because a note
147
+ marked complete is a note nobody looks at again.
148
+
149
+ **You do not have to compute the hash.** Write anything twelve characters long and send the plan:
150
+ `plan set` refuses a hash that is not the note's current one and names the correct one, so one
151
+ round trip gets you every hash in the plan. That refusal is the point of the field — a hash that
152
+ was never right makes the note read as edited-since from the moment the milestone ships, and it
153
+ drops back into a backlog nobody planned.
154
+
155
+ ### `model` — tables, and what their columns HOLD
156
+
157
+ Each field carries a `classification`: `public`, `internal`, `personal` or `sensitive`. That is what
158
+ lets a permission claim be checked against the data rather than only against itself — a function
159
+ returning a `personal` column with no permission rule is a defect the gate can name. It is also what
160
+ `db/annotations.ts` ends up expressing, so plan it here rather than discovering it at migrate time.
161
+
162
+ Each relationship carries `onDelete`: `cascade`, `restrict` or `orphan`. A foreign key states which
163
+ rows are related; it does not state what the product wants when the parent goes, and those three
164
+ produce identical schemas until someone deletes something. A `cascade` is checked against the
165
+ migrations by `plan progress`, and needs `provedBy` naming a scenario in this same plan that deletes
166
+ the parent and asserts the children are gone.
167
+
168
+ A table that already exists is altered by a NEW forward migration, numbered on from the ones in
169
+ `db/sqlite/`. Editing an applied migration is the hash mismatch that makes a deployed database
170
+ refuse to migrate, so plan the alter as its own file.
171
+
172
+ ### `functions` — with their wire and their rule on them
173
+
174
+ The wire and the permission live ON the function, because that is where pikku enforces them. Two
175
+ parallel lists are two lists that drift.
176
+
177
+ **Do not give a function a `wire`.** pikku already serves every `expose: true` function as an RPC
178
+ and the client calls it by name, so for nearly every function there is nothing to decide — leave the
179
+ field out. A `wire` is for the exceptions: its own HTTP path via `wireHTTP` (a webhook, a payment
180
+ callback, a public URL another system posts to), a queue job, a channel, a scheduled task, or a
181
+ workflow entry point. Those last two are not alternate URLs — they are what the milestone IS, and a
182
+ plan that omits them ships a `status` column nothing advances or a job nobody runs.
183
+
184
+ `permission` is a SENTENCE, not a role name — "only the person who wrote it can edit it". The roles
185
+ are the engineer's choice; the rule is the part that has to survive being implemented, in the
186
+ function's `permissions` field and never in its body. `null` means open to anyone signed in, and
187
+ stating that is different from omitting it. **Every function with a permission rule needs a
188
+ permission scenario naming it in `fn`** — a rule with no failing case is a claim, not a check, and
189
+ `plan set` refuses the plan without one.
190
+
191
+ ### `scopes` — what a KIND of user may do, never who owns a row
192
+
193
+ A scope depends ONLY on the session: "may this kind of user do this at all" — `admin:invoices:void`,
194
+ `billing`. It is declared with `wireScope` and granted in `mapSession`, so every name here has to
195
+ end up in pikku's generated scope meta. One that cannot be declared is one the build can never
196
+ finish, and `plan progress` refuses the milestone for as long as it stands.
197
+
198
+ Ownership is not a scope. "Only the owner of the house may read it" depends on the row being asked
199
+ for, and a scope never sees the row — that is the function's `permission` sentence and lives nowhere
200
+ else. If the rule mentions the record, it is a `permission`; if it reads the same for every row that
201
+ user touches, it is a scope. An app built from one person's idea usually has none at all, so
202
+ `{"kind": "n/a", ...}` is the ordinary answer here.
203
+
204
+ ### `roles` — and the app each one signs into
205
+
206
+ The distinct `app` values across `roles` ARE the frontends this project gets, and nothing downstream
207
+ can recover the answer. Colleagues share ONE app and differ by nav and permitted actions (the
208
+ mechanic, the person on the counter, the bookkeeper); someone across the counter with an account
209
+ gets their own (the customer, the tenant, the patient). One app is a real answer and often the right
210
+ one — then every role carries the same slug. Never invent a person the notes do not name in order to
211
+ reach two, and never give a slug to someone who never signs in: a guest checking out takes the same
212
+ slug as the seller they buy from, on that app's public routes outside `/app`. Once there is more
213
+ than one app, every `ui` item carries its `app` too.
214
+
215
+ Adding the second frontend is the BUILD's job, at the milestone that first needs it —
216
+ pikku-build's multi-app reference. Your part is recording which app each person is in.
217
+
218
+ ### `ui` — routes, and what is on them
219
+
220
+ One item per route, each with the pass that builds it. A pass-1 route has to be LINKED to the
221
+ scenario that proves it, and there are two ways: name the scenario in that `ui` item's own
222
+ `scenarios` array, or write a browser scenario whose `feature` contains the route path. Nothing else
223
+ counts — an unlinked browser scenario reads as a route nobody proved, and the plan is refused.
224
+
225
+ ### `scenarios` — keyed by level
226
+
227
+ `backend`, `browser`, `permission`, each its own slot. Keyed rather than tagged so that a plan with
228
+ four backend scenarios and no browser scenario fails on its SHAPE — a flat list lets that through,
229
+ and that is exactly the milestone that builds an API and ships no screen.
230
+
231
+ **Every scenario needs `name`: the `pikkuScenario` export it becomes** (`saveEntryScenario`).
232
+ `feature` and `scenario` are prose for a reader, and prose cannot be matched against codegen.
233
+ `plan progress` looks for the export by that exact name, so a scenario with no name is one the gate
234
+ cannot see.
235
+
236
+ Permission scenarios default to pass 2 — they harden a journey that has to exist before they can
237
+ cover it — so a role × resource cross product there costs the milestone nothing.
238
+
239
+ ---
240
+
241
+ ## What makes a plan wrong
242
+
243
+ `plan set` catches the mechanical failures. These are the ones it cannot:
244
+
245
+ - **A plan for a different milestone.** The note is about `entries`; the plan builds `projects`.
246
+ Every entity the note names must appear in a function or a table.
247
+ - **A pass 1 that is a layer, not a slice.** "Pass 1: the data model. Pass 2: the API. Pass 3: the
248
+ screens." That is three passes of nothing working.
249
+ - **Scenarios that assert the code ran rather than that the person got what they came for.** A
250
+ scenario proving `saveEntry` returns 200 proves the wire. The one worth planning is the one where
251
+ a person writes something, comes back, and it is still there. A browser scenario that opens a page
252
+ and asserts it is still on it proves the route loads and nothing else —
253
+ `pikku knowledge plan progress` names it as a problem and refuses the milestone.
254
+ - **A permission rule invented here.** If the notes do not say who may do a thing, the answer is
255
+ `null` with the reason, not a rule you made up. A rule the user never agreed to is one they find
256
+ out about by being locked out of their own app.
257
+
258
+ ---
259
+
260
+ ## When you are done
261
+
262
+ The accepted `plan set` is the end of the seat. Hand the milestone to `pikku-build`, which reads the
263
+ plan with `plan show --for-build`, builds it, and closes the milestone only when
264
+ `pikku knowledge plan progress` is clean. What you wrote is what it is measured against.