@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/skills",
3
- "version": "0.12.22",
3
+ "version": "0.12.26",
4
4
  "description": "The Pikku agent skills — the instruction set coding agents read to build, wire and deploy Pikku projects",
5
5
  "author": "yasser.fadl@gmail.com",
6
6
  "license": "MIT",
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: pikku-a11y
3
+ description: >-
4
+ Accessibility rules (WCAG 2.2) for the app UI: labeled inputs, real buttons/links, keyboard and focus, contrast and not-color-alone, modals, reduced motion.
5
+ TRIGGER when: building forms or any interactive UI, icon-only buttons, modals/drawers, tables/lists with actions, keyboard/focus work, or the user mentions accessibility / screen readers / WCAG.
6
+ DO NOT TRIGGER when: working on backend functions, database, or deployment with no UI.
7
+ installGroups: [client]
8
+ ---
9
+
10
+ # Accessibility Rules
11
+
12
+ Mantine components are accessible ONLY when used properly — the rules below are the
13
+ "properly". They apply to every page; heading order, landmarks, and image alt text are
14
+ covered in the `pikku-seo` skill and apply app-wide, not just on public pages.
15
+
16
+ ## Every input has a label
17
+
18
+ - Use the `label` prop on every Mantine input — a placeholder is NOT a label (it
19
+ disappears on input and is never announced as one). Placeholder = example value only.
20
+ - Use the `error` and `description` props for validation/help text — Mantine associates
21
+ them with the input for screen readers; a loose `<Text c="red">` next to the field
22
+ does not.
23
+ - Icon-only controls (`ActionIcon`, icon `Button`) MUST have `aria-label={m.key()}`
24
+ naming the action ("Delete item", not "Trash icon").
25
+
26
+ ## Interactive = a real button or link
27
+
28
+ - Never `onClick` on a `div`/`Box`/`Card` — it is invisible to keyboard and screen
29
+ readers. Use `Button`, `ActionIcon`, `UnstyledButton`, or `<Link>`; navigation is a
30
+ link (href), actions are buttons.
31
+ - Everything reachable by Tab, activatable by Enter/Space. Never remove focus outlines
32
+ (the theme owns the focus ring), never set `tabIndex` greater than 0, never trap focus
33
+ yourself.
34
+ - Whole-row/whole-card click: put the button/link INSIDE with the row as its label —
35
+ don't make the container clickable and unfocusable.
36
+
37
+ ## Don't say it with color alone
38
+
39
+ - Status must carry text or an icon, not only a color: a Badge says "Overdue", a form
40
+ error has a message — a red tint by itself is invisible to colorblind users.
41
+ - Contrast comes from the theme; don't undermine it by stacking `c="dimmed"` on small
42
+ text over tinted backgrounds. Body copy stays at least AA-readable.
43
+ - Touch targets: WCAG 2.2 minimum 24px — don't shrink `ActionIcon`/`Checkbox` below
44
+ size `sm`, and keep adjacent row actions spaced.
45
+
46
+ ## Overlays and motion
47
+
48
+ - Modals/drawers: use Mantine `Modal`/`Drawer` and ALWAYS pass `title` — that is what
49
+ gets announced; focus trap and Escape come built in. (This project uses drawers, not
50
+ dialogs.)
51
+ - Landing-page animation (the only custom-CSS surface) respects
52
+ `prefers-reduced-motion: reduce` — gate transforms/parallax behind the media query.
53
+
54
+ ## Self-check before declaring UI done
55
+
56
+ Tab through the page once: every control reachable and visibly focused, every input
57
+ labeled, every icon button named, every status readable without color. A browser
58
+ scenario proves the flow works, not that it is reachable without a mouse — this
59
+ manual pass is the only check that does.
@@ -5,7 +5,7 @@ description: >-
5
5
  ref(), pikkuAddonServices, pikkuAddonWireServices, addon package structure, and cross-project
6
6
  function sharing. TRIGGER when: code uses wireAddon/ref()/pikkuAddonServices, user asks about
7
7
  addons, reusable function packages, cross-project sharing, or addon package structure. DO NOT
8
- TRIGGER when: user asks about internal function composition (use pikku-rpc) or general function
8
+ TRIGGER when: user asks about internal function composition (use pikku-wiring) or general function
9
9
  definitions (use pikku-concepts).
10
10
  installGroups: [core]
11
11
  ---
@@ -159,7 +159,7 @@ export const createSingletonServices = pikkuAddonServices(
159
159
 
160
160
  `secrets` and `variables` arrive **typed against the addon's own declarations**,
161
161
  and a secret is a `SecretValue` — `.reveal()` is the only way to the plaintext
162
- (see `pikku-config`). `pikkuAddonConfig` is the matching factory for the addon's
162
+ (see `pikku-services`). `pikkuAddonConfig` is the matching factory for the addon's
163
163
  config object.
164
164
 
165
165
  ### `pikkuAddonWireServices(factory)`
@@ -1,322 +1,73 @@
1
1
  ---
2
2
  name: pikku-agent
3
3
  description: >-
4
- Use when building AI agents, chatbots, or LLM-powered assistants with Pikku. Covers
5
- pikkuAgent, ref() tool registration, memory, streaming, tool approval, thread ownership, and
6
- invocation via rpc.agent. TRIGGER when: code uses pikkuAgent/rpc.agent/runAgent/
7
- streamAgent, user asks about AI agents, chatbots, LLM assistants, tool-calling agents, agent
8
- memory/streaming, or `pikku enable agent`. DO NOT TRIGGER when: user asks about MCP tool
9
- exposure (use pikku-mcp) or general function definitions (use pikku-concepts).
4
+ Use when building AI agents, chatbots or LLM-powered assistants with Pikku — pikkuAgent, ref()
5
+ tool registration, memory, streaming, tool approval, thread ownership, invocation via rpc.agent,
6
+ the VercelAgentRunner and its provider map, and the voiceInput/voiceOutput middlewares. TRIGGER
7
+ when: code uses pikkuAgent/rpc.agent/runAgent/streamAgent/VercelAgentRunner/voiceInput, user asks
8
+ about AI agents, chatbots, tool-calling, agent memory or streaming, model providers, speech in or
9
+ out, or `pikku enable agent`. DO NOT TRIGGER when: user asks about MCP tool exposure (use
10
+ pikku-wiring), workflows (use pikku-workflow), or general function definitions (use
11
+ pikku-concepts).
10
12
  installGroups: [core]
11
13
  ---
12
14
 
13
- # Pikku AI Agent Wiring
14
-
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
-
25
- Build AI agents that use Pikku functions as tools. Agents support conversation memory, streaming, and multi-step tool execution.
26
-
27
- ## Before You Start
28
-
29
- ```bash
30
- pikku info functions --verbose # See existing functions that can be used as agent tools
31
- pikku info tags --verbose # Understand project organization
32
- ```
33
-
34
- See `pikku-concepts` for the core mental model.
35
-
36
- ## API Reference
37
-
38
- ### `pikkuAgent(config)`
39
-
40
- Import it from the generated agent types file — `#pikku` does not re-export it:
41
-
42
- ```typescript
43
- import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
44
- import { ref } from '#pikku/function'
45
-
46
- pikkuAgent({
47
- name: string, // Unique agent identifier
48
- description: string, // What the agent does (shown in agent listings)
49
- summary?: string,
50
- errors?: string[],
51
-
52
- // --- system prompt: three fields, joined role → personality → goal ---
53
- role?: string, // Who it is: 'You are a support engineer triaging bugs.'
54
- personality?: string, // How it sounds: tone, verbosity
55
- goal: string, // REQUIRED — what it is for
56
-
57
- model: string, // e.g. 'openai/gpt-5-mini'
58
- temperature?: number,
59
- providerOptions?: { // passed through untouched, keyed by provider
60
- openai?: { reasoningEffort?: 'minimal' | ... },
61
- },
62
-
63
- // --- capabilities: all three take ref() handles, not imported values ---
64
- tools?: unknown[], // ref('todos:addTodo'), ref('graph:sleep'), …
65
- agents?: unknown[], // sub-agents to delegate to
66
- workflows?: unknown[], // workflows callable as a tool
67
- agentMode?: 'delegate' | 'supervise',
68
-
69
- memory?: {
70
- storage?: string, // Service name for persistence (e.g. 'agentStorage')
71
- vector?: string, // Vector store service name
72
- embedder?: string, // Embedding service name
73
- lastMessages?: number, // How many messages to retain in context
74
- workingMemory?: ZodSchema, // Schema for structured working memory
75
- },
76
-
77
- maxSteps?: number, // Max tool-call rounds per invocation
78
- toolChoice?: 'auto' | 'required' | 'none',
79
- prepareStep?: (ctx) => void, // See "Narrowing tools per step"
80
- input?: ZodSchema,
81
- output?: ZodSchema, // Structured output — only honoured with NO tools
82
- tags?: string[],
83
-
84
- sessionScope?: 'user' | 'org', // Who owns this agent's threads. Default 'user'
85
- auth?: boolean, // Default false — see below
86
- scopes?: ScopeId[], // AND gate, checked before permissions
87
- permissions?: PermissionGroup,
88
-
89
- middleware?: PikkuMiddleware[],
90
- channelMiddleware?: PikkuChannelMiddleware[],
91
- agentMiddleware?: PikkuAgentMiddlewareHooks[],
92
- })
93
- ```
94
-
95
- **`goal` is the required prompt field, not `instructions`** — there is no
96
- `instructions` key. `role`/`personality`/`goal` are concatenated in that order,
97
- and nothing validates which text lands in which, so the split is purely for
98
- legibility: prose in the "wrong" one still reaches the model.
99
-
100
- **Tools are `ref('domain:funcName')` handles, not imported function values.** The
101
- inspector resolves the ref against the generated function map, which is what lets
102
- an agent reach a function in another package (or a `graph:*` builtin) without an
103
- import cycle.
104
-
105
- `auth` defaults to `false` because agents are usually invoked from an
106
- already-authenticated `pikkuFunc`. `scopes` and `permissions` are enforced either
107
- way — see `pikku-permissions`.
108
-
109
- ### Invoking an agent
110
-
111
- From inside a Pikku function, go through `wire.rpc.agent` — it carries the
112
- session, credentials, and RPC depth for you:
113
-
114
- ```typescript
115
- const result = await rpc.agent.run('todo-agent', {
116
- message, threadId, resourceId, // required
117
- attachments?, model?, temperature?, context?,
118
- })
119
-
120
- await rpc.agent.stream('todo-agent', input) // writes to the wire's channel
121
- await rpc.agent.approve(runId, [{ toolCallId, approved }], expectedAgentName?)
122
- await rpc.agent.resume(runId, { toolCallId, approved })
123
- await rpc.agent.interrupt(runId, 'user' | 'speech' | 'timeout')
124
- ```
125
-
126
- `context` is a string injected into the system prompt for this request only —
127
- use it for upfront state (current org, project, deployment) so the agent stops
128
- asking the user for identifiers it could have been handed.
129
-
130
- `run` resolves to:
131
-
132
- ```typescript
133
- {
134
- runId, threadId, text,
135
- object?, // set when the agent has an `output` schema
136
- steps, // tool calls made
137
- usage: { inputTokens, outputTokens },
138
- status?: 'completed' | 'suspended',
139
- pendingApprovals?: [{ toolCallId, toolName, args, reason?, runId }],
140
- }
141
- ```
142
-
143
- `runAgent` / `streamAgent` from `@pikku/core/agent` are the layer beneath
144
- this. Their third argument is `RunAgentParams` (`{ sessionService?,
145
- getCredential?, anonymousOwnerResourceId? }`) — **not** `{ singletonServices }`.
146
- Reach for them only outside a wired function; inside one, `rpc.agent` is the
147
- supported path.
148
-
149
- ### Stream events
150
-
151
- `rpc.agent.stream` pushes `AgentStreamEvent`s onto the channel:
152
-
153
- ```typescript
154
- // { type: 'step-start', stepNumber }
155
- // { type: 'text-delta' | 'reasoning-delta', text }
156
- // { type: 'tool-call', toolCallId, toolName, args }
157
- // { type: 'tool-result', toolCallId, toolName, result }
158
- // { type: 'agent-call' | 'agent-result', agentName, session, input | result }
159
- // { type: 'approval-request', toolCallId, toolName, args, reason?, runId? }
160
- // { type: 'credential-request', toolCallId, toolName, credentialName,
161
- // credentialType: 'oauth2' | 'apikey', connectUrl?, runId }
162
- // { type: 'usage', tokens: { input, output }, model }
163
- // { type: 'transcript', text } // what the user was heard to say
164
- // { type: 'audio-delta', data, format, text? } | { type: 'audio-done' }
165
- // { type: 'data', name, data } | { type: 'generative-ui', spec }
166
- // { type: 'suspended', reason: 'rpc-missing', missingRpcs }
167
- // { type: 'interrupted', runId, text, reason }
168
- // { type: 'error', message }
169
- // { type: 'done' }
170
- ```
171
-
172
- Every event except `agent-call`/`agent-result`/`suspended` also carries optional
173
- `agent` and `session` fields, so a UI can attribute output to a sub-agent rather
174
- than folding it into the parent's transcript.
175
-
176
- ## Usage Patterns
177
-
178
- ### Define an Agent
179
-
180
- ```typescript
181
- import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
182
- import { ref } from '#pikku/function'
183
-
184
- export const todoAgent = pikkuAgent({
185
- name: 'todo-agent',
186
- description: 'Manages a todo list',
187
- goal: 'You help users manage their todos. You can list, add, complete and delete them.',
188
- model: 'openai/gpt-5-mini',
189
- tools: [
190
- ref('todos:listTodos'),
191
- ref('todos:addTodo'),
192
- ref('todos:completeTodo'),
193
- ref('graph:sleep'),
194
- ],
195
- memory: { storage: 'agentStorage', lastMessages: 20 },
196
- maxSteps: 10,
197
- toolChoice: 'auto',
198
- })
199
- ```
200
-
201
- ### Scaffold the HTTP surface
202
-
203
- ```bash
204
- pikku enable agent
205
- ```
206
-
207
- The next `pikku all` generates `agent.gen.ts` — run/stream/approve/resume
208
- callers plus thread listing endpoints, with thread ownership already enforced
209
- against the session. Don't hand-write these routes.
210
-
211
- ### Structured output
212
-
213
- An `output` schema fills `result.object`, but **only when the agent exposes no
214
- tools** — with a tool present the runner falls back to free text, silently. If
215
- you need both, split the classification into its own tool-free agent.
216
-
217
- ```typescript
218
- export const structuredAgent = pikkuAgent({
219
- name: 'structured-agent',
220
- description: 'Classifies a message and returns a structured verdict',
221
- goal: 'You classify the sentiment of the user message.',
222
- model: 'openai/gpt-5-mini',
223
- output: z.object({ sentiment: z.string(), score: z.number() }),
224
- })
225
- ```
226
-
227
- ### Narrowing tools per step
228
-
229
- `prepareStep` runs before each step with the live tool array for that step, so
230
- mutating it in place changes what the model is offered from there on. `stop()`
231
- ends the loop — called before step 0 the run completes with an empty result
232
- rather than signalling that it was short-circuited.
233
-
234
- ```typescript
235
- prepareStep: ({ stepNumber, tools }) => {
236
- if (stepNumber >= 1) tools.length = 0 // withdraw tools after the first step
237
- }
238
- ```
239
-
240
- ### Tool approval
241
-
242
- A tool that should pause for a human sets `approvalRequired: true` (with an
243
- optional `approvalDescription`) on the _function_, not on the agent. The run then
244
- resolves with `status: 'suspended'` and `pendingApprovals`, and streaming emits
245
- `approval-request`. Answer with `rpc.agent.approve(runId, approvals)`.
246
-
247
- Authorization around tools is two-layer: an agent only sees tools its session can
248
- reach, and the function's own `permissions` still guard the call when the model
249
- picks one.
250
-
251
- ### Thread ownership
252
-
253
- `resourceId` is caller-supplied but never trusted as an owner. The session's
254
- principal (`userId`, or `orgId` when `sessionScope: 'org'`) is prefixed onto it,
255
- so a client can sub-partition inside its own boundary and cannot read across one.
256
- A sessionless run gets an ephemeral anonymous owner instead.
257
-
258
- ## Complete Example
259
-
260
- ```typescript
261
- // functions/todos.functions.ts
262
- export const listTodos = pikkuSessionlessFunc({
263
- description: 'List all todo items',
264
- func: async ({ db }, { status }) => {
265
- return { todos: await db.listTodos(status) }
266
- },
267
- })
268
-
269
- export const createTodo = pikkuFunc({
270
- description: 'Create a new todo item',
271
- func: async ({ db }, { text, priority, dueDate }) => {
272
- return await db.createTodo({ text, priority, dueDate })
273
- },
274
- })
275
-
276
- export const completeTodo = pikkuFunc({
277
- description: 'Mark a todo as complete',
278
- func: async ({ db }, { todoId }) => {
279
- return await db.completeTodo(todoId)
280
- },
281
- })
282
-
283
- // agents/todo-assistant.agent.ts
284
- import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
285
- import { ref } from '#pikku/function'
286
-
287
- export const todoAssistant = pikkuAgent({
288
- name: 'todo-assistant',
289
- description: 'A helpful assistant that manages todos',
290
- role: 'You are an assistant that manages a user’s todo list.',
291
- personality: 'Concise. One short paragraph unless asked for detail.',
292
- goal: `Keep the user's todos accurate.
293
- - When creating todos, infer priority if not specified
294
- - When listing todos, summarize the results`,
295
- model: 'openai/gpt-5-mini',
296
- tools: [
297
- ref('todos:listTodos'),
298
- ref('todos:createTodo'),
299
- ref('todos:completeTodo'),
300
- ],
301
- memory: { storage: 'agentStorage', lastMessages: 20 },
302
- maxSteps: 5,
303
- temperature: 0.7,
304
- })
305
-
306
- // Wire to HTTP for a chat endpoint — or skip this entirely and run
307
- // `pikku enable agent`, which scaffolds run/stream/approve/resume for you.
308
- wireHTTP({
309
- method: 'post',
310
- route: '/chat',
311
- func: pikkuFunc({
312
- title: 'Chat',
313
- func: async (_services, { message, threadId }, { session, rpc }) => {
314
- return await rpc.agent.run('todo-assistant', {
315
- message,
316
- threadId,
317
- resourceId: session.userId,
318
- })
319
- },
320
- }),
321
- })
322
- ```
15
+ # Pikku AI Agents
16
+
17
+ Signatures and option keys come from `pikku doc` — run `pikku doc --ai` for the
18
+ installed surface. This skill is the part the compiler cannot tell you: how an
19
+ agent reaches the rest of the app, and which of its knobs mean something other
20
+ than what they look like.
21
+
22
+ ## Pick the reference
23
+
24
+ | You are… | Read |
25
+ | --- | --- |
26
+ | Defining or invoking an agent — tools, memory, streaming, approval, threads | `references/agents.md` |
27
+ | Wiring the runner, or pointing model strings at a provider or gateway | `references/runner-vercel.md` |
28
+ | Adding speech in or out of an agent | `references/voice.md` |
29
+
30
+ ## An agent is a function that reaches other functions
31
+
32
+ Tools, sub-agents and workflows are all supplied as `ref('domain:funcName')`
33
+ handles rather than imported values. The inspector resolves each ref against the
34
+ generated function map, which is what lets an agent call into another package or
35
+ a `graph:*` builtin without an import cycle — and what lets the tool menu be
36
+ filtered per session before the model ever sees it.
37
+
38
+ Invoke through `wire.rpc.agent` from inside a Pikku function; it carries the
39
+ session, the credentials and the RPC depth for you.
40
+
41
+ ## The knobs that do not mean what they look like
42
+
43
+ - **`goal` is the prompt field, and it is required.** There is no `instructions`
44
+ key. `role`, `personality` and `goal` are concatenated in that order and
45
+ nothing validates which text lands where, so the split buys legibility only.
46
+ - **`output` is honoured only when the agent has no tools.** A structured-output
47
+ schema on a tool-calling agent is silently inert.
48
+ - **`auth` defaults to `false`**, because agents are normally invoked from an
49
+ already-authenticated `pikkuFunc`. `scopes` and `permissions` are enforced
50
+ either way — see `pikku-auth`.
51
+ - **`approvalRequired` sits on the tool function, not on the agent.** The run
52
+ then resolves `status: 'suspended'` with `pendingApprovals`; answer with
53
+ `rpc.agent.approve(runId, approvals)`.
54
+ - **Model strings split on the first slash only** — `provider/model`, so
55
+ `'ollama/qwen2.5:7b'` is fine and a string with no slash throws rather than
56
+ defaulting to a provider.
57
+
58
+ ## What NOT to do
59
+
60
+ - **Do not trust a caller-supplied `resourceId` as an owner.** It never is: the
61
+ session's principal (`userId`, or `orgId` under `sessionScope: 'org'`) is
62
+ prefixed onto it, so a client sub-partitions inside its own boundary and cannot
63
+ read across one. A sessionless run gets an ephemeral anonymous owner.
64
+ - **Do not rely on the tool menu alone for authorization.** It is two-layer — the
65
+ session decides which tools an agent can see, and the function's own
66
+ `permissions` still guard the call when the model picks one.
67
+ - **Do not point the `'*'` provider at a single vendor.** It resolves every
68
+ provider name with no exact entry, so aimed at one vendor an `anthropic/…`
69
+ string silently reaches OpenAI. Point it at a gateway or a scripted test
70
+ provider — something that genuinely accepts arbitrary model names.
71
+ - **Do not add `@pikku/ai-voice` as a dependency.** It still publishes but its
72
+ entire source is `export {}`. Voice is two middlewares in `@pikku/core/agent`,
73
+ with the speech models reached through the `agentRunner`.