@keystrokehq/skills 0.0.1

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 (44) hide show
  1. package/AGENTS-blurb.md +123 -0
  2. package/LICENSE +21 -0
  3. package/README.md +63 -0
  4. package/keystroke-agent-authoring/SKILL.md +225 -0
  5. package/keystroke-agent-authoring/evals/evals.json +29 -0
  6. package/keystroke-agent-authoring/references/messaging-gateways.md +242 -0
  7. package/keystroke-agent-authoring/references/patterns.md +417 -0
  8. package/keystroke-agent-authoring/references/prebuilt-integrations.md +879 -0
  9. package/keystroke-agent-authoring/references/sandbox-and-mcp.md +214 -0
  10. package/keystroke-agent-authoring/references/source-map.md +182 -0
  11. package/keystroke-agent-authoring/references/testing.md +85 -0
  12. package/keystroke-cli-workspace/SKILL.md +93 -0
  13. package/keystroke-cli-workspace/evals/evals.json +23 -0
  14. package/keystroke-cli-workspace/references/command-map.md +50 -0
  15. package/keystroke-cli-workspace/references/credentials-and-connect.md +79 -0
  16. package/keystroke-cli-workspace/references/project-lifecycle.md +85 -0
  17. package/keystroke-credential-binding/SKILL.md +509 -0
  18. package/keystroke-credential-binding/evals/evals.json +29 -0
  19. package/keystroke-credential-binding/references/cli.md +85 -0
  20. package/keystroke-credential-binding/references/patterns.md +878 -0
  21. package/keystroke-credential-binding/references/source-map.md +69 -0
  22. package/keystroke-data-toolkit/SKILL.md +59 -0
  23. package/keystroke-data-toolkit/evals/evals.json +23 -0
  24. package/keystroke-data-toolkit/references/usage.md +79 -0
  25. package/keystroke-task-authoring/SKILL.md +124 -0
  26. package/keystroke-task-authoring/evals/evals.json +23 -0
  27. package/keystroke-task-authoring/references/patterns.md +132 -0
  28. package/keystroke-task-authoring/references/source-map.md +61 -0
  29. package/keystroke-trigger-authoring/SKILL.md +189 -0
  30. package/keystroke-trigger-authoring/evals/evals.json +29 -0
  31. package/keystroke-trigger-authoring/references/patterns.md +265 -0
  32. package/keystroke-trigger-authoring/references/source-map.md +128 -0
  33. package/keystroke-trigger-authoring/references/testing.md +148 -0
  34. package/keystroke-workflow-as-tool-debugging/SKILL.md +52 -0
  35. package/keystroke-workflow-as-tool-debugging/evals/evals.json +23 -0
  36. package/keystroke-workflow-as-tool-debugging/references/playbook.md +77 -0
  37. package/keystroke-workflow-authoring/SKILL.md +234 -0
  38. package/keystroke-workflow-authoring/evals/evals.json +29 -0
  39. package/keystroke-workflow-authoring/references/patterns.md +265 -0
  40. package/keystroke-workflow-authoring/references/prebuilt-integrations.md +811 -0
  41. package/keystroke-workflow-authoring/references/runtime-helpers.md +264 -0
  42. package/keystroke-workflow-authoring/references/source-map.md +108 -0
  43. package/keystroke-workflow-authoring/references/testing.md +108 -0
  44. package/package.json +26 -0
@@ -0,0 +1,242 @@
1
+ # Messaging Gateway Patterns
2
+
3
+ Read this file when the user wants a Keystroke agent to be entered through Slack, GitHub, or Linear conversations.
4
+
5
+ ## What a gateway is
6
+
7
+ Use `MessagingGateway` on `Agent.messaging` when the agent should receive and continue conversations through a messaging surface.
8
+
9
+ File layout reminder:
10
+ - keep each exported gateway in its own `*.gateway.ts` file
11
+ - keep the agent in its own `*.agent.ts` file
12
+ - keep the gateway credential set in its own `*.credential-set.ts` file
13
+
14
+ Teach this vocabulary clearly:
15
+ - a messaging gateway is a conversation entrypoint for an agent
16
+ - a messaging gateway is not a workflow trigger
17
+ - a messaging gateway is not a task
18
+ - workflows and tasks can still call agents, but conversational entry belongs on the agent
19
+
20
+ ## Core fields
21
+
22
+ - `id`
23
+ - `name`
24
+ - `description`
25
+ - `provider`
26
+ - `mode`
27
+ - `credentialSet`
28
+ - `credentialScope`
29
+ - `appRef`
30
+
31
+ ## Platform gateway (default)
32
+
33
+ Use this when the user wants messaging through the Keystroke-managed app. This is the default unless the user says otherwise.
34
+
35
+ ```ts
36
+ import { CredentialSet, MessagingGateway } from '@keystrokehq/core';
37
+ import { z } from 'zod';
38
+
39
+ export const slackGatewayCredentials = new CredentialSet({
40
+ id: 'slackGateway',
41
+ auth: z.object({
42
+ SLACK_BOT_TOKEN: z.string(),
43
+ }),
44
+ });
45
+
46
+ export const supportSlackGateway = new MessagingGateway({
47
+ id: 'support-slack',
48
+ name: 'Support Slack Gateway',
49
+ provider: 'slack',
50
+ mode: 'platform',
51
+ credentialSet: slackGatewayCredentials,
52
+ credentialScope: 'organization',
53
+ });
54
+ ```
55
+
56
+ ## Custom app gateway
57
+
58
+ Use this only when the user explicitly wants to use their own app or create a new app with a specific name.
59
+
60
+ ```ts
61
+ import { CredentialSet, MessagingGateway } from '@keystrokehq/core';
62
+ import { z } from 'zod';
63
+
64
+ export const customSlackCredentials = new CredentialSet({
65
+ id: 'slackCustom',
66
+ auth: z.object({
67
+ botToken: z.string(),
68
+ signingSecret: z.string(),
69
+ }),
70
+ });
71
+
72
+ export const customSlackGateway = new MessagingGateway({
73
+ id: 'sales-slack',
74
+ name: 'Sales Slack Gateway',
75
+ provider: 'slack',
76
+ mode: 'custom',
77
+ credentialSet: customSlackCredentials,
78
+ credentialScope: 'organization',
79
+ appRef: 'sales-bot',
80
+ });
81
+ ```
82
+
83
+ Key differences from the platform gateway:
84
+ - `mode` is `'custom'` instead of `'platform'`
85
+ - `appRef` identifies which user-created app this gateway connects to
86
+ - the credential set typically requires more fields because the user supplies all auth values
87
+
88
+ ## Prebuilt gateway helpers from `@keystroke/integration-slack`
89
+
90
+ Integration packages ship prebuilt gateway factories so users don't need to wire credential sets manually. Prefer these over hand-constructing a `MessagingGateway` with `@keystrokehq/core` when the provider has one available.
91
+
92
+ ### Platform gateway (prebuilt)
93
+
94
+ ```ts
95
+ import { slackPlatformGateway } from '@keystroke/integration-slack/platform';
96
+
97
+ export const supportGateway = slackPlatformGateway();
98
+ ```
99
+
100
+ `slackPlatformGateway()` returns a `mode: 'platform'` gateway with the Keystroke-managed Slack installation credentials already configured. Override `id`, `name`, or `description` when needed:
101
+
102
+ ```ts
103
+ export const supportGateway = slackPlatformGateway({
104
+ id: 'support-slack',
105
+ name: 'Support Slack Gateway',
106
+ description: 'Handles support conversations in Slack.',
107
+ });
108
+ ```
109
+
110
+ ### Custom app gateway (prebuilt)
111
+
112
+ ```ts
113
+ import { slackCustomGateway } from '@keystroke/integration-slack/custom';
114
+
115
+ export const salesGateway = slackCustomGateway({
116
+ appRef: 'sales-bot',
117
+ });
118
+ ```
119
+
120
+ `slackCustomGateway()` returns a `mode: 'custom'` gateway using the user-provided Slack app credentials. `appRef` identifies which custom app installation to route through. Override `id`, `name`, or `description` when needed:
121
+
122
+ ```ts
123
+ export const salesGateway = slackCustomGateway({
124
+ appRef: 'sales-bot',
125
+ id: 'sales-slack',
126
+ name: 'Sales Slack Gateway',
127
+ });
128
+ ```
129
+
130
+ ### When to use prebuilt vs manual
131
+
132
+ - Use the prebuilt helpers when the provider integration package exports them — they already bundle the correct credential sets and mode.
133
+ - Fall back to manual `new MessagingGateway({...})` when no prebuilt helper exists for the provider, or when the credential set needs to differ from the integration default.
134
+
135
+ ## Agent with messaging
136
+
137
+ Using a prebuilt gateway:
138
+
139
+ ```ts
140
+ import { anthropic } from '@keystroke/integration-ai';
141
+ import { Agent } from '@keystrokehq/core';
142
+ import { slackPlatformGateway } from '@keystroke/integration-slack/platform';
143
+
144
+ export const supportAgent = new Agent({
145
+ id: 'support-agent',
146
+ name: 'Support Agent',
147
+ systemPrompt: 'Answer support requests and use tools when needed.',
148
+ model: 'anthropic/claude-sonnet-4-20250514',
149
+ credentialSets: [anthropic],
150
+ messaging: [slackPlatformGateway()],
151
+ });
152
+ ```
153
+
154
+ Or importing a gateway from its own file:
155
+
156
+ ```ts
157
+ import { anthropic } from '@keystroke/integration-ai';
158
+ import { Agent } from '@keystrokehq/core';
159
+ import { supportSlackGateway } from './support-slack.gateway';
160
+
161
+ export const supportAgent = new Agent({
162
+ id: 'support-agent',
163
+ name: 'Support Agent',
164
+ systemPrompt: 'Answer support requests and use tools when needed.',
165
+ model: 'anthropic/claude-sonnet-4-20250514',
166
+ credentialSets: [anthropic],
167
+ messaging: [supportSlackGateway],
168
+ });
169
+ ```
170
+
171
+ ## `provider`
172
+
173
+ Current provider values in `core` are:
174
+ - `slack`
175
+ - `linear`
176
+ - `github`
177
+
178
+ Choose the provider that matches the conversation surface the user wants.
179
+
180
+ ## `mode` — platform app vs custom app
181
+
182
+ There are two kinds of messaging app a gateway can use. Teach this distinction clearly:
183
+
184
+ ### Platform app (`mode: 'platform'`)
185
+
186
+ A **platform app** is a Keystroke-owned and Keystroke-managed app (e.g. a Slack bot, GitHub App, or Linear integration) that the user **installs** into their workspace, organization, or repo. The user does not create or configure this app themselves — Keystroke provides it and handles the OAuth installation flow.
187
+
188
+ - **This is the default.** When the user asks for a messaging gateway without specifying that they want to create their own app or use a differently-named app, use `mode: 'platform'`.
189
+ - Platform gateways do not need `appRef` because the platform already knows which app to use.
190
+ - Credentials are resolved through the platform's installation flow, not manually provided by the user.
191
+
192
+ ### Custom app (`mode: 'custom'`)
193
+
194
+ A **custom app** is an app that the user **creates on their own** in the provider's developer portal (e.g. a custom Slack app, a self-managed GitHub App). The user provides their own credentials (bot tokens, signing secrets, app IDs, private keys, etc.).
195
+
196
+ - Use `mode: 'custom'` only when the user explicitly says they want to create their own app, use their own app, or reference a specific app name that is not the Keystroke platform app.
197
+ - Custom gateways typically include `appRef` to identify which custom app installation to route through.
198
+ - The credential set for a custom gateway usually requires more fields than a platform gateway because the user must supply all auth values directly.
199
+
200
+ ### Choosing the right mode
201
+
202
+ | Signal from the user | Mode |
203
+ |---|---|
204
+ | "connect Slack" / "add Slack messaging" / no app preference stated | `platform` |
205
+ | "I already have a Slack app" / "use my own app" | `custom` |
206
+ | "create a Slack app called support-bot" | `custom` with `appRef` |
207
+ | "use the Keystroke Slack app" | `platform` |
208
+
209
+ When in doubt, default to `platform`.
210
+
211
+ ## `credentialScope`
212
+
213
+ Use credential scope when the gateway credentials should resolve at:
214
+ - `organization`
215
+ - `project`
216
+
217
+ This is a gateway-specific authoring concern and should be cross-linked with the credential skill.
218
+
219
+ ## `appRef`
220
+
221
+ Use `appRef` only with `mode: 'custom'` to identify which user-created app installation the gateway routes through.
222
+
223
+ - Platform gateways should not set `appRef` — the platform knows which app to use.
224
+ - Custom gateways should set `appRef` to a stable name that identifies the user's app (e.g. `'support-bot'`, `'sales-bot'`).
225
+
226
+ ## Prebuilt helpers by provider
227
+
228
+ Integration packages and `core` export gateway factory functions so users don't need to construct gateways manually:
229
+
230
+ - **Slack**: `slackPlatformGateway()` from `@keystroke/integration-slack/platform`, `slackCustomGateway()` from `@keystroke/integration-slack/custom`
231
+ - **GitHub**: `githubPlatformGateway()` from `@keystrokehq/core`
232
+
233
+ Use these when the helper fits. Fall back to `new MessagingGateway({...})` when no helper exists for the provider or the credential set needs to differ.
234
+
235
+ ## Gotchas
236
+
237
+ - Default to `mode: 'platform'` unless the user explicitly asks to use their own app or create a custom app.
238
+ - Do not use `appRef` on platform gateways.
239
+ - Do not explain a messaging gateway as a workflow trigger.
240
+ - Do not attach a messaging gateway to a workflow.
241
+ - Do not put gateway setup in the system prompt.
242
+ - Keep credentials on the gateway or related credential set, not in `process.env` inside authored code.
@@ -0,0 +1,417 @@
1
+ # Agent And Tool / Operation Examples
2
+
3
+ Use these examples when the user needs concrete patterns for public agent APIs.
4
+
5
+ `Operation`, `Step`, and `Tool` are aliases for the same class. In this agent reference, examples default to `Tool` because that name reads best from agent context.
6
+
7
+ Some later snippets reuse symbols from earlier snippets in this file, such as `crmCredentials` or `deleteRecordTool`.
8
+
9
+ File layout reminder:
10
+
11
+ - keep every exported primitive in its own typed file
12
+ - `*.agent.ts` exports one agent
13
+ - `*.tool.ts`, `*.step.ts`, or `*.operation.ts` exports one operation
14
+ - `*.credential-set.ts`, `*.sandbox.ts`, and `*.mcp-server.ts` each export one primitive
15
+ - when a snippet reuses `crmCredentials`, `lookupCustomerTool`, or another primitive, import it from its own typed file
16
+
17
+ ## Minimal `Agent`
18
+
19
+ ```ts
20
+ import { anthropic } from '@keystroke/integration-ai';
21
+ import { Agent } from '@keystrokehq/core';
22
+
23
+ export const summarizer = new Agent({
24
+ id: 'summarizer',
25
+ name: 'Summarizer',
26
+ systemPrompt: 'Summarize the input in one paragraph.',
27
+ model: 'anthropic/claude-sonnet-4-20250514',
28
+ credentialSets: [anthropic],
29
+ });
30
+ ```
31
+
32
+ This is the smallest runnable authored agent:
33
+
34
+ - `id` identifies the agent
35
+ - `systemPrompt` tells the model how to behave
36
+ - `model` selects the model
37
+
38
+ ## `Agent` with optional fields
39
+
40
+ ```ts
41
+ import { anthropic } from '@keystroke/integration-ai';
42
+ import { Agent, CredentialSet } from '@keystrokehq/core';
43
+ import { z } from 'zod';
44
+
45
+ const searchCredentials = new CredentialSet({
46
+ id: 'searchApi',
47
+ name: 'Search API',
48
+ auth: z.object({
49
+ apiKey: z.string(),
50
+ }),
51
+ });
52
+
53
+ export const researchAgent = new Agent({
54
+ id: 'research-agent',
55
+ name: 'Research Agent',
56
+ description: 'Finds information and writes a short summary.',
57
+ systemPrompt: 'Search first. Summarize second. Cite the key facts you found.',
58
+ model: 'anthropic/claude-sonnet-4-20250514',
59
+ credentialSets: [anthropic, searchCredentials],
60
+ maxSteps: 12,
61
+ });
62
+ ```
63
+
64
+ Use this shape when the agent needs metadata, credentials, or a larger step budget.
65
+
66
+ ## Custom `Tool`
67
+
68
+ ```ts
69
+ import { Tool } from '@keystrokehq/core';
70
+ import { z } from 'zod';
71
+
72
+ export const formatTicketTool = new Tool({
73
+ id: 'format_ticket',
74
+ name: 'Format Ticket',
75
+ description: 'Formats a support ticket for downstream systems.',
76
+ input: z.object({
77
+ title: z.string(),
78
+ priority: z.enum(['low', 'medium', 'high']),
79
+ }),
80
+ output: z.object({
81
+ text: z.string(),
82
+ }),
83
+ run: (input) => {
84
+ return {
85
+ text: `[${input.priority.toUpperCase()}] ${input.title}`,
86
+ };
87
+ },
88
+ });
89
+ ```
90
+
91
+ The same example could also be written with `new Operation({...})` if the unit is shared outside agent code.
92
+
93
+ ## Workflow Tool
94
+
95
+ ```ts
96
+ import { anthropic } from '@keystroke/integration-ai';
97
+ import { Agent, Workflow } from '@keystrokehq/core';
98
+ import { z } from 'zod';
99
+ import { lookupCustomerTool } from './lookup-customer.tool';
100
+
101
+ export const auditAccountWorkflow = new Workflow({
102
+ id: 'audit-account',
103
+ name: 'Audit Account',
104
+ description: 'Collects customer facts and returns audit findings.',
105
+ largeResultMode: 'ref',
106
+ input: z.object({
107
+ accountId: z.string(),
108
+ }),
109
+ output: z.object({
110
+ findings: z.array(z.string()),
111
+ }),
112
+ run: async () => ({
113
+ findings: [],
114
+ }),
115
+ });
116
+
117
+ export const supportAgent = new Agent({
118
+ id: 'support-agent',
119
+ name: 'Support Agent',
120
+ systemPrompt: 'Use account data before answering account-specific questions.',
121
+ model: 'anthropic/claude-sonnet-4-20250514',
122
+ credentialSets: [anthropic],
123
+ tools: [lookupCustomerTool, auditAccountWorkflow],
124
+ });
125
+ ```
126
+
127
+ Workflow tools are enriched at build time. Sync workflows return direct results. Suspending workflows yield and resume later; hook workflows use `provide_workflow_response`, and large outputs can become refs inspected with `describe_ref`, `read_ref`, and `slice_ref`.
128
+
129
+ Use `midSessionSnapshot: true` on the workflow only when measured workloads benefit from Phase D snapshot replay. The default yield path is simpler and usually preferred.
130
+
131
+ ## `Tool` with `needsApproval`
132
+
133
+ ```ts
134
+ import { Tool } from '@keystrokehq/core';
135
+ import { z } from 'zod';
136
+
137
+ const deleteRecordTool = new Tool({
138
+ id: 'delete_record',
139
+ name: 'Delete Record',
140
+ description: 'Deletes a record from the remote system.',
141
+ needsApproval: true,
142
+ input: z.object({
143
+ recordId: z.string(),
144
+ }),
145
+ output: z.object({
146
+ deleted: z.boolean(),
147
+ }),
148
+ run: async () => ({ deleted: true }),
149
+ });
150
+ ```
151
+
152
+ ## `Tool` with `credentialSets` and `workflowGlobals`
153
+
154
+ ```ts
155
+ import { CredentialSet, Tool } from '@keystrokehq/core';
156
+ import { z } from 'zod';
157
+
158
+ const crmCredentials = new CredentialSet({
159
+ id: 'crmApi',
160
+ name: 'CRM API',
161
+ auth: z.object({
162
+ apiKey: z.string(),
163
+ }),
164
+ });
165
+
166
+ const lookupCustomerTool = new Tool({
167
+ id: 'lookup_customer',
168
+ name: 'Lookup Customer',
169
+ description: 'Loads a customer for the current tenant.',
170
+ credentialSets: [crmCredentials],
171
+ workflowGlobals: z.object({
172
+ tenantId: z.string(),
173
+ }),
174
+ input: z.object({
175
+ customerId: z.string(),
176
+ }),
177
+ output: z.object({
178
+ keyUsed: z.string(),
179
+ tenantId: z.string(),
180
+ }),
181
+ run: async (_input, ctx) => {
182
+ return {
183
+ keyUsed: ctx.credentials.crmApi.apiKey,
184
+ tenantId: ctx.workflowGlobals.tenantId,
185
+ };
186
+ },
187
+ });
188
+ ```
189
+
190
+ Use this when the tool depends on both credentials and workflow-provided runtime values.
191
+
192
+ ## Runnable `Agent`
193
+
194
+ ```ts
195
+ import { Agent, Tool } from '@keystrokehq/core';
196
+ import { z } from 'zod';
197
+
198
+ const echoTool = new Tool({
199
+ id: 'echo',
200
+ name: 'Echo',
201
+ description: 'Returns the same text.',
202
+ input: z.object({ text: z.string() }),
203
+ output: z.object({ text: z.string() }),
204
+ run: (input) => ({ text: input.text }),
205
+ });
206
+
207
+ export const rawAgent = new Agent({
208
+ id: 'raw-agent',
209
+ name: 'Raw Agent',
210
+ description: 'Example low-level agent.',
211
+ systemPrompt: 'Use the tool output when it helps.',
212
+ model: 'anthropic/claude-sonnet-4-20250514',
213
+ input: z.object({
214
+ text: z.string(),
215
+ }),
216
+ output: z.object({
217
+ ok: z.boolean(),
218
+ }),
219
+ tools: [echoTool],
220
+ maxSteps: 5,
221
+ run: async () => {
222
+ return {
223
+ output: { ok: true },
224
+ session: {
225
+ id: 'session_123',
226
+ agentId: 'raw-agent',
227
+ messages: [],
228
+ createdAt: new Date(),
229
+ updatedAt: new Date(),
230
+ },
231
+ steps: 1,
232
+ toolCalls: [],
233
+ };
234
+ },
235
+ });
236
+ ```
237
+
238
+ This form is useful when:
239
+
240
+ - the agent needs explicit `input` and `output` schemas
241
+ - the agent should provide its own custom `run(...)`
242
+ - the agent should provide its own custom `stream(...)`
243
+
244
+ ## `agent.run(...)`
245
+
246
+ ```ts
247
+ const result = await researchAgent.run('Research the account and summarize the findings.');
248
+ ```
249
+
250
+ Use `run(...)` when the caller wants the final agent result.
251
+
252
+ ## `agent.stream(...)`
253
+
254
+ ```ts
255
+ for await (const event of researchAgent.stream('Research the account and summarize the findings.')) {
256
+ // Handle streamed event.type here.
257
+ }
258
+ ```
259
+
260
+ Use `stream(...)` when the caller wants incremental events such as:
261
+
262
+ - text
263
+ - tool calls
264
+ - tool results
265
+ - completion
266
+
267
+ ## Agent hooks
268
+
269
+ ```ts
270
+ import { anthropic } from '@keystroke/integration-ai';
271
+ import { Agent } from '@keystrokehq/core';
272
+
273
+ export const guardedAgent = new Agent({
274
+ id: 'guarded-agent',
275
+ name: 'Guarded Agent',
276
+ systemPrompt: 'Never call destructive tools without approval.',
277
+ model: 'anthropic/claude-sonnet-4-20250514',
278
+ credentialSets: [anthropic],
279
+ hooks: {
280
+ onToolStart: async (tool, input) => {
281
+ if (tool === 'delete_record') {
282
+ return {
283
+ allow: false,
284
+ replacement: {
285
+ blocked: true,
286
+ originalInput: input,
287
+ },
288
+ };
289
+ }
290
+
291
+ return { allow: true };
292
+ },
293
+ onToolEnd: async (tool, result) => {
294
+ // Observe the completed tool result here.
295
+ },
296
+ onStep: async (step) => {
297
+ // Observe each agent step here.
298
+ },
299
+ },
300
+ });
301
+ ```
302
+
303
+ Hook usage:
304
+
305
+ - `onToolStart`: inspect or block a tool call before it runs
306
+ - `onToolEnd`: observe the tool result after it runs
307
+ - `onStep`: observe each model/tool step
308
+
309
+ ## `Tool.run(...)`
310
+
311
+ ```ts
312
+ const result = await formatTicketTool.run({
313
+ title: 'Reset password',
314
+ priority: 'high',
315
+ });
316
+ ```
317
+
318
+ If the tool has `credentialSets` or `workflowGlobals`, pass an explicit context:
319
+
320
+ ```ts
321
+ const result = await lookupCustomerTool.run(
322
+ { customerId: 'cus_123' },
323
+ {
324
+ credentials: {
325
+ crmApi: {
326
+ apiKey: 'test-key',
327
+ },
328
+ },
329
+ workflowGlobals: {
330
+ tenantId: 'tenant_123',
331
+ },
332
+ }
333
+ );
334
+ ```
335
+
336
+ ## `Tool.configure(...)`
337
+
338
+ ```ts
339
+ const renamedFormatTool = formatTicketTool.configure({
340
+ description: 'Formats a support ticket for CRM import.',
341
+ });
342
+ ```
343
+
344
+ ## `Tool.mapInput(...)`
345
+
346
+ ```ts
347
+ import { z } from 'zod';
348
+
349
+ const formatTicketFromString = formatTicketTool.mapInput(
350
+ z.string(),
351
+ (title) => ({
352
+ title,
353
+ priority: 'medium',
354
+ })
355
+ );
356
+ ```
357
+
358
+ ## `Tool.mapOutput(...)`
359
+
360
+ ```ts
361
+ import { z } from 'zod';
362
+
363
+ const formatTicketTextOnly = formatTicketTool.mapOutput(
364
+ z.string(),
365
+ (output) => output.text
366
+ );
367
+ ```
368
+
369
+ ## `Tool.describe()` and `Tool.toManifest()`
370
+
371
+ The same instance methods are available on `Operation`, because `Tool` is just the agent-oriented alias.
372
+
373
+ ```ts
374
+ const toolSummary = formatTicketTool.describe();
375
+ const toolManifest = formatTicketTool.toManifest();
376
+ ```
377
+
378
+ ## `skills`
379
+
380
+ ```ts
381
+ import { anthropic } from '@keystroke/integration-ai';
382
+ import { Agent } from '@keystrokehq/core';
383
+
384
+ export const codingAgent = new Agent({
385
+ id: 'coding-agent',
386
+ name: 'Coding Agent',
387
+ systemPrompt: 'Use the installed skills before inventing your own process.',
388
+ model: 'anthropic/claude-sonnet-4-20250514',
389
+ credentialSets: [anthropic],
390
+ skills: [
391
+ { path: './.keystroke/skills/repo-linting' },
392
+ { registry: 'keystroke', name: 'typescript-review' },
393
+ ],
394
+ });
395
+ ```
396
+
397
+ Use `skills` when the agent runtime should have access to installed skills in addition to tools or MCP servers.
398
+
399
+ ## `configure(...)`
400
+
401
+ ```ts
402
+ const renamedAgent = researchAgent.configure({
403
+ name: 'Renamed Research Agent',
404
+ maxSteps: 20,
405
+ });
406
+ ```
407
+
408
+ Use `configure(...)` to create a new agent instance with changed metadata or hooks.
409
+
410
+ ## `describe()` and `toManifest()`
411
+
412
+ ```ts
413
+ const summary = researchAgent.describe();
414
+ const manifest = researchAgent.toManifest();
415
+ ```
416
+
417
+ Use `describe()` for a short human-readable summary and `toManifest()` when the caller needs the manifest form.