@guildai/cli 0.6.2 → 0.7.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 (55) hide show
  1. package/README.md +3 -1
  2. package/dist/commands/agent/chat.js +41 -99
  3. package/dist/commands/agent/clone.js +1 -1
  4. package/dist/commands/agent/code.js +1 -1
  5. package/dist/commands/agent/fork.js +2 -2
  6. package/dist/commands/agent/get.js +1 -1
  7. package/dist/commands/agent/grep.js +2 -2
  8. package/dist/commands/agent/init.js +10 -3
  9. package/dist/commands/agent/list.js +9 -1
  10. package/dist/commands/agent/publish.js +1 -1
  11. package/dist/commands/agent/revalidate.js +1 -1
  12. package/dist/commands/agent/save.js +23 -2
  13. package/dist/commands/agent/test.js +70 -130
  14. package/dist/commands/agent/unpublish.js +1 -1
  15. package/dist/commands/agent/update.js +1 -1
  16. package/dist/commands/agent/versions.js +1 -1
  17. package/dist/commands/agent/workspaces.js +1 -1
  18. package/dist/commands/chat.d.ts +2 -1
  19. package/dist/commands/chat.js +189 -88
  20. package/dist/commands/config/list.js +2 -2
  21. package/dist/commands/integration/operation/create.js +2 -2
  22. package/dist/commands/integration/operation/list.js +2 -2
  23. package/dist/commands/integration/update.js +1 -1
  24. package/dist/commands/integration/version/get.js +2 -2
  25. package/dist/commands/integration/version/publish.js +2 -2
  26. package/dist/commands/integration/version/test.js +2 -2
  27. package/dist/commands/session/events.js +7 -3
  28. package/dist/commands/session/interrupt.d.ts +3 -0
  29. package/dist/commands/session/interrupt.js +33 -0
  30. package/dist/commands/setup.js +70 -11
  31. package/dist/commands/workspace/get.js +1 -1
  32. package/dist/commands/workspace/list.js +28 -6
  33. package/dist/commands/workspace/select.js +40 -9
  34. package/dist/components/TaskView.js +2 -2
  35. package/dist/index.js +2 -0
  36. package/dist/lib/agent-helpers.d.ts +59 -2
  37. package/dist/lib/agent-helpers.js +153 -8
  38. package/dist/lib/alternate-screen.js +2 -0
  39. package/dist/lib/api-client.js +2 -1
  40. package/dist/lib/config.d.ts +3 -0
  41. package/dist/lib/config.js +33 -0
  42. package/dist/lib/event-filter.d.ts +50 -0
  43. package/dist/lib/event-filter.js +91 -0
  44. package/dist/lib/generated-types.d.ts +2 -0
  45. package/dist/lib/generated-types.js +20 -0
  46. package/dist/lib/session-events.d.ts +27 -2
  47. package/dist/lib/session-events.js +5 -3
  48. package/dist/lib/session-polling.d.ts +8 -0
  49. package/dist/lib/session-polling.js +49 -0
  50. package/dist/lib/spinners.js +4 -1
  51. package/docs/CLI_WORKFLOW.md +7 -1
  52. package/docs/DESIGN.md +1 -1
  53. package/docs/skills/codex-agent-dev.md +155 -0
  54. package/docs/skills/integrations.md +338 -0
  55. package/package.json +1 -1
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Event type filtering for the CLI `--events` flag.
3
+ *
4
+ * Mirrors the web UI's "Filter by type" modal, which groups event types into
5
+ * two categories: user-facing events (on by default) and system/debug events
6
+ * (off by default).
7
+ *
8
+ * The canonical event type list is generated from the backend EventType enum
9
+ * (see generated-types.ts). The user/system grouping is a UI concern defined
10
+ * here and in www/src/components/EventTypeFilters/types.ts.
11
+ */
12
+ import type { EventType } from './generated-types.js';
13
+ /**
14
+ * User-facing event types — shown by default (mirrors web UI defaults).
15
+ */
16
+ export declare const USER_EVENT_TYPES: readonly EventType[];
17
+ /**
18
+ * System / debug event types — hidden by default (mirrors web UI defaults).
19
+ */
20
+ export declare const SYSTEM_EVENT_TYPES: readonly EventType[];
21
+ /**
22
+ * Default active filter: all user-facing types, no system types.
23
+ */
24
+ export declare const DEFAULT_EVENT_TYPES: Set<string>;
25
+ /**
26
+ * Parse the value of the `--events` flag into a Set of event type names.
27
+ *
28
+ * Whatever you pass replaces the defaults entirely:
29
+ * - `none` → empty set (no event types shown)
30
+ * - `user` → all USER_EVENT_TYPES (same as default)
31
+ * - `system` → only SYSTEM_EVENT_TYPES
32
+ * - `all` → both USER_EVENT_TYPES + SYSTEM_EVENT_TYPES
33
+ *
34
+ * Comma-separated for fine-grained control:
35
+ * - `agent_console,llm_start` → only those two types
36
+ * - `user,system` → same as `all`
37
+ */
38
+ export declare function parseEventFilter(raw: string): Set<string>;
39
+ /**
40
+ * Check whether an event should be shown given the active filter.
41
+ *
42
+ * Returns `true` when the event type is in the filter set.
43
+ *
44
+ * Note: certain event types (e.g. `agent_notification_progress`,
45
+ * `agent_notification_message`) drive core UI state (spinner, chat history)
46
+ * and are always processed regardless of the filter; only their *display* is
47
+ * gated here for new/optional event types.
48
+ */
49
+ export declare function shouldShowEvent(type: string, filter: Set<string>): boolean;
50
+ //# sourceMappingURL=event-filter.d.ts.map
@@ -0,0 +1,91 @@
1
+ // Copyright 2026 Guild.ai
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * User-facing event types — shown by default (mirrors web UI defaults).
5
+ */
6
+ export const USER_EVENT_TYPES = [
7
+ 'user_message',
8
+ 'agent_notification_message',
9
+ 'agent_notification_progress',
10
+ 'agent_notification_error',
11
+ 'credentials_request',
12
+ 'agent_install_request',
13
+ 'trigger_message',
14
+ 'system_error',
15
+ ];
16
+ /**
17
+ * System / debug event types — hidden by default (mirrors web UI defaults).
18
+ */
19
+ export const SYSTEM_EVENT_TYPES = [
20
+ 'agent_console',
21
+ 'runtime_start',
22
+ 'runtime_running',
23
+ 'runtime_waiting',
24
+ 'runtime_error',
25
+ 'runtime_done',
26
+ 'llm_start',
27
+ 'llm_done',
28
+ ];
29
+ /**
30
+ * Default active filter: all user-facing types, no system types.
31
+ */
32
+ export const DEFAULT_EVENT_TYPES = new Set(USER_EVENT_TYPES);
33
+ /**
34
+ * Parse the value of the `--events` flag into a Set of event type names.
35
+ *
36
+ * Whatever you pass replaces the defaults entirely:
37
+ * - `none` → empty set (no event types shown)
38
+ * - `user` → all USER_EVENT_TYPES (same as default)
39
+ * - `system` → only SYSTEM_EVENT_TYPES
40
+ * - `all` → both USER_EVENT_TYPES + SYSTEM_EVENT_TYPES
41
+ *
42
+ * Comma-separated for fine-grained control:
43
+ * - `agent_console,llm_start` → only those two types
44
+ * - `user,system` → same as `all`
45
+ */
46
+ export function parseEventFilter(raw) {
47
+ const tokens = raw
48
+ .split(',')
49
+ .map((t) => t.trim())
50
+ .filter(Boolean);
51
+ // `none` returns an empty set — no event types shown
52
+ if (tokens.length === 1 && tokens[0] === 'none') {
53
+ return new Set();
54
+ }
55
+ const result = new Set();
56
+ for (const token of tokens) {
57
+ switch (token) {
58
+ case 'all':
59
+ for (const t of USER_EVENT_TYPES)
60
+ result.add(t);
61
+ for (const t of SYSTEM_EVENT_TYPES)
62
+ result.add(t);
63
+ break;
64
+ case 'user':
65
+ for (const t of USER_EVENT_TYPES)
66
+ result.add(t);
67
+ break;
68
+ case 'system':
69
+ for (const t of SYSTEM_EVENT_TYPES)
70
+ result.add(t);
71
+ break;
72
+ default:
73
+ result.add(token);
74
+ }
75
+ }
76
+ return result;
77
+ }
78
+ /**
79
+ * Check whether an event should be shown given the active filter.
80
+ *
81
+ * Returns `true` when the event type is in the filter set.
82
+ *
83
+ * Note: certain event types (e.g. `agent_notification_progress`,
84
+ * `agent_notification_message`) drive core UI state (spinner, chat history)
85
+ * and are always processed regardless of the filter; only their *display* is
86
+ * gated here for new/optional event types.
87
+ */
88
+ export function shouldShowEvent(type, filter) {
89
+ return filter.has(type);
90
+ }
91
+ //# sourceMappingURL=event-filter.js.map
@@ -2,4 +2,6 @@ export declare const WEBHOOK_SERVICES: readonly ["AZURE_DEVOPS", "BITBUCKET", "C
2
2
  export type WebhookService = (typeof WEBHOOK_SERVICES)[number];
3
3
  export declare const TIME_TRIGGER_FREQUENCIES: readonly ["HOURLY", "DAILY", "WEEKLY", "MONTHLY", "CRON"];
4
4
  export type TimeTriggerFrequency = (typeof TIME_TRIGGER_FREQUENCIES)[number];
5
+ export declare const EVENT_TYPES: readonly ["user_message", "agent_console", "runtime_start", "runtime_running", "runtime_waiting", "runtime_error", "runtime_done", "credentials_request", "agent_install_request", "agent_notification_message", "agent_notification_progress", "agent_notification_error", "system_error", "trigger_message", "interrupted", "llm_start", "llm_done"];
6
+ export type EventType = (typeof EVENT_TYPES)[number];
5
7
  //# sourceMappingURL=generated-types.d.ts.map
@@ -32,4 +32,24 @@ export const TIME_TRIGGER_FREQUENCIES = [
32
32
  'MONTHLY',
33
33
  'CRON',
34
34
  ];
35
+ // Source: python/guildcore/routes/serializers.py -> EventType
36
+ export const EVENT_TYPES = [
37
+ 'user_message',
38
+ 'agent_console',
39
+ 'runtime_start',
40
+ 'runtime_running',
41
+ 'runtime_waiting',
42
+ 'runtime_error',
43
+ 'runtime_done',
44
+ 'credentials_request',
45
+ 'agent_install_request',
46
+ 'agent_notification_message',
47
+ 'agent_notification_progress',
48
+ 'agent_notification_error',
49
+ 'system_error',
50
+ 'trigger_message',
51
+ 'interrupted',
52
+ 'llm_start',
53
+ 'llm_done',
54
+ ];
35
55
  //# sourceMappingURL=generated-types.js.map
@@ -54,7 +54,7 @@ export declare function getAgentName(agent: AgentRef | null | undefined): string
54
54
  * Check if a task's agent matches a target agent identifier.
55
55
  *
56
56
  * Target format: "@scope/owner~name" (e.g. "@guildai/guildai~agent-builder") or simple name ("assistant")
57
- * AgentRef format: { name: "agent-builder", full_name: "guildai/agent-builder", ... }
57
+ * AgentRef format: { name: "agent-builder", full_name: "guildai~agent-builder", ... }
58
58
  */
59
59
  export declare function matchesAgent(taskAgent: AgentRef | null | undefined, targetAgent: string): boolean;
60
60
  /** Get display name for a task - agent name or tool name */
@@ -117,6 +117,31 @@ export interface AgentConsoleEvent extends BaseEvent {
117
117
  level: 'debug' | 'info' | 'warn' | 'error';
118
118
  content: string;
119
119
  }
120
+ export interface TriggerMessageEvent extends BaseEvent {
121
+ type: 'trigger_message';
122
+ content: {
123
+ type: 'text';
124
+ data: string;
125
+ };
126
+ }
127
+ export interface SystemErrorEvent extends BaseEvent {
128
+ type: 'system_error';
129
+ content: {
130
+ type: 'text';
131
+ data: string;
132
+ };
133
+ details?: Record<string, unknown>;
134
+ }
135
+ export interface LlmStartEvent extends BaseEvent {
136
+ type: 'llm_start';
137
+ provider: string;
138
+ payload: Record<string, unknown>;
139
+ }
140
+ export interface LlmDoneEvent extends BaseEvent {
141
+ type: 'llm_done';
142
+ status_code: number;
143
+ body: string;
144
+ }
120
145
  /** Agent info as returned in install request */
121
146
  export interface RequestedAgent {
122
147
  id: string;
@@ -149,7 +174,7 @@ export interface InterruptedEvent extends BaseEvent {
149
174
  interrupted_at: string;
150
175
  interrupted_by_id: string;
151
176
  }
152
- export type SessionEvent = UserMessageEvent | RuntimeStartEvent | RuntimeRunningEvent | RuntimeWaitingEvent | RuntimeErrorEvent | RuntimeDoneEvent | AgentNotificationMessageEvent | AgentNotificationProgressEvent | AgentNotificationErrorEvent | AgentConsoleEvent | AgentInstallRequestEvent | CredentialsRequestEvent | InterruptedEvent;
177
+ export type SessionEvent = UserMessageEvent | RuntimeStartEvent | RuntimeRunningEvent | RuntimeWaitingEvent | RuntimeErrorEvent | RuntimeDoneEvent | AgentNotificationMessageEvent | AgentNotificationProgressEvent | AgentNotificationErrorEvent | AgentConsoleEvent | TriggerMessageEvent | SystemErrorEvent | LlmStartEvent | LlmDoneEvent | AgentInstallRequestEvent | CredentialsRequestEvent | InterruptedEvent;
153
178
  export interface Session {
154
179
  id: string;
155
180
  workspace_id?: string;
@@ -41,7 +41,7 @@ export function getAgentName(agent) {
41
41
  * Check if a task's agent matches a target agent identifier.
42
42
  *
43
43
  * Target format: "@scope/owner~name" (e.g. "@guildai/guildai~agent-builder") or simple name ("assistant")
44
- * AgentRef format: { name: "agent-builder", full_name: "guildai/agent-builder", ... }
44
+ * AgentRef format: { name: "agent-builder", full_name: "guildai~agent-builder", ... }
45
45
  */
46
46
  export function matchesAgent(taskAgent, targetAgent) {
47
47
  if (!taskAgent)
@@ -49,9 +49,11 @@ export function matchesAgent(taskAgent, targetAgent) {
49
49
  // Direct name match for simple identifiers like "assistant"
50
50
  if (taskAgent.name === targetAgent)
51
51
  return true;
52
- // Normalize "@scope/owner~name" -> "owner/name" for full_name comparison
52
+ // Normalize target to match full_name format:
53
+ // 1. Strip npm scope prefix: "@scope/owner~name" -> "owner~name"
54
+ // 2. Convert slash separator to tilde: "owner/name" -> "owner~name"
53
55
  if (taskAgent.full_name) {
54
- const normalized = targetAgent.replace(/^@[^/]+\//, '').replace('~', '/');
56
+ const normalized = targetAgent.replace(/^@[^/]+\//, '').replace('/', '~');
55
57
  if (taskAgent.full_name === normalized)
56
58
  return true;
57
59
  }
@@ -16,4 +16,12 @@ export interface PollResult {
16
16
  * 3. runtime_error from agent tasks — fail fast
17
17
  */
18
18
  export declare function pollForResponse(client: GuildAPIClient, sessionId: string, afterEventId: string | undefined, maxWaitTime?: number): Promise<PollResult>;
19
+ /**
20
+ * Poll for agent response while streaming matching events to stdout as JSONL.
21
+ *
22
+ * Same completion logic as pollForResponse(), but writes each event that
23
+ * passes the filter to stdout so callers get intermediate output (console.log,
24
+ * progress, etc.) in JSON/JSONL automation modes.
25
+ */
26
+ export declare function pollForResponseWithEvents(client: GuildAPIClient, sessionId: string, eventFilter: Set<string>, afterEventId: string | undefined, maxWaitTime?: number): Promise<PollResult>;
19
27
  //# sourceMappingURL=session-polling.d.ts.map
@@ -1,6 +1,7 @@
1
1
  // Copyright 2026 Guild.ai
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import { debug, isDebugMode } from './errors.js';
4
+ import { shouldShowEvent } from './event-filter.js';
4
5
  import { fetchEvents } from './session-events-fetch.js';
5
6
  /**
6
7
  * Poll for agent response using from_id cursor.
@@ -50,4 +51,52 @@ export async function pollForResponse(client, sessionId, afterEventId, maxWaitTi
50
51
  }
51
52
  return { response: null, lastEventId: fromId };
52
53
  }
54
+ /**
55
+ * Poll for agent response while streaming matching events to stdout as JSONL.
56
+ *
57
+ * Same completion logic as pollForResponse(), but writes each event that
58
+ * passes the filter to stdout so callers get intermediate output (console.log,
59
+ * progress, etc.) in JSON/JSONL automation modes.
60
+ */
61
+ export async function pollForResponseWithEvents(client, sessionId, eventFilter, afterEventId, maxWaitTime = 60000) {
62
+ const startTime = Date.now();
63
+ let fromId = afterEventId;
64
+ while (Date.now() - startTime < maxWaitTime) {
65
+ const events = await fetchEvents(client, sessionId, { fromId });
66
+ let lastAgentRuntimeDone = null;
67
+ for (const event of events) {
68
+ debug(`pollForResponseWithEvents event: ${event.type}`);
69
+ // Stream matching events to stdout as JSONL
70
+ if (shouldShowEvent(event.type, eventFilter)) {
71
+ process.stdout.write(JSON.stringify(event) + '\n');
72
+ }
73
+ if (event.type === 'agent_notification_message') {
74
+ return { response: event.content.data, lastEventId: event.id };
75
+ }
76
+ if (event.type === 'runtime_done' &&
77
+ event.content !== undefined &&
78
+ event.task &&
79
+ 'agent' in event.task) {
80
+ lastAgentRuntimeDone = JSON.stringify(event.content);
81
+ }
82
+ if (event.type === 'runtime_error' && event.task && 'agent' in event.task) {
83
+ return {
84
+ response: JSON.stringify({ error: event.content }),
85
+ lastEventId: event.id,
86
+ };
87
+ }
88
+ if (event.type === 'agent_console' && isDebugMode()) {
89
+ process.stderr.write(`[console.${event.level}] ${event.content}\n`);
90
+ }
91
+ }
92
+ if (events.length > 0) {
93
+ fromId = events[events.length - 1].id;
94
+ }
95
+ if (lastAgentRuntimeDone !== null) {
96
+ return { response: lastAgentRuntimeDone, lastEventId: fromId };
97
+ }
98
+ await new Promise((resolve) => setTimeout(resolve, 2000));
99
+ }
100
+ return { response: null, lastEventId: fromId };
101
+ }
53
102
  //# sourceMappingURL=session-polling.js.map
@@ -751,7 +751,10 @@ export function getSpinnerThemes() {
751
751
  * @deprecated Use createSpinner() instead
752
752
  */
753
753
  export function getActiveTheme() {
754
- return getSpinnerThemes().get('classic');
754
+ const theme = getSpinnerThemes().get('classic');
755
+ if (!theme)
756
+ throw new Error('Missing classic spinner theme');
757
+ return theme;
755
758
  }
756
759
  /**
757
760
  * @deprecated Use createSpinner() instead
@@ -57,11 +57,17 @@ guild agent save --message "Description" --wait --publish
57
57
  ### Project Setup
58
58
 
59
59
  ```bash
60
- # Install Guild CLI skills for coding assistants (Claude Code, etc.)
60
+ # Install Guild CLI skills for coding assistants (Claude Code, Codex, etc.)
61
61
  guild setup
62
62
 
63
63
  # Also create a CLAUDE.md template
64
64
  guild setup --claude-md
65
+
66
+ # Install Codex skill files
67
+ guild setup --codex
68
+
69
+ # Also create an AGENTS.md template for Codex
70
+ guild setup --codex --agents-md
65
71
  ```
66
72
 
67
73
  ### Creating Agents
package/docs/DESIGN.md CHANGED
@@ -188,7 +188,7 @@ Environments are Docker containers managed by GuildCore. The CLI provides a simp
188
188
 
189
189
  ## Integration with Developer Tools
190
190
 
191
- The CLI integrates with AI coding assistants like Claude Code, Cursor, and others. Run `guild setup` to install Guild skills into your project.
191
+ The CLI integrates with AI coding assistants like Claude Code, Cursor, and others. Run `guild setup` to install Claude Code skills into your project, or `guild setup --codex` to install Codex skills.
192
192
 
193
193
  The CLI also exposes a [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server via `guild mcp`. This gives coding assistants direct access to Guild's API — searching agents, managing workspaces, reading contexts, and starting sessions — without leaving the editor. `guild setup` configures it by default; use `--no-mcp` to skip.
194
194
 
@@ -0,0 +1,155 @@
1
+ ---
2
+ name: guild-agent-dev
3
+ description: Use when working on Guild agents with Codex: creating agents, editing agent code, testing with the Guild CLI, saving versions, publishing, debugging sessions, or answering questions about Guild agent development.
4
+ ---
5
+
6
+ # Guild Agent Development
7
+
8
+ Use the Guild CLI for local agent development. Prefer commands that are scriptable and safe for a coding agent to run.
9
+
10
+ ## First Checks
11
+
12
+ Before changing an agent, establish the local context:
13
+
14
+ ```bash
15
+ guild --version
16
+ guild auth status
17
+ guild workspace current
18
+ ```
19
+
20
+ If authentication or workspace selection is missing, tell the user the exact command to run:
21
+
22
+ ```bash
23
+ guild auth login
24
+ guild workspace select
25
+ ```
26
+
27
+ ## Core Rules
28
+
29
+ - Use `guild agent init`, `guild agent clone`, `guild agent pull`, `guild agent test`, `guild agent chat`, and `guild agent save` for agent lifecycle work.
30
+ - Do not edit `guild.json`; it is managed by the CLI.
31
+ - Prefer `guild agent save -A --message "..."` instead of raw `git add`, `git commit`, or `git push`.
32
+ - Do not use `git push` directly for agent repositories; use `guild agent save`.
33
+ - Keep edits scoped to the agent files requested by the user.
34
+ - Surface command output that matters, especially validation errors, session links, workspace IDs, and next commands.
35
+
36
+ ## Common Workflows
37
+
38
+ Create a new agent:
39
+
40
+ ```bash
41
+ mkdir my-agent && cd my-agent
42
+ guild agent init --name my-agent --template LLM
43
+ ```
44
+
45
+ Clone an existing agent:
46
+
47
+ ```bash
48
+ guild agent clone owner/agent-name
49
+ cd agent-name
50
+ ```
51
+
52
+ Sync before editing when collaborating:
53
+
54
+ ```bash
55
+ guild agent pull
56
+ ```
57
+
58
+ Test unsaved local changes:
59
+
60
+ ```bash
61
+ guild agent test --ephemeral
62
+ ```
63
+
64
+ For non-interactive Codex test loops, prefer JSON input:
65
+
66
+ ```bash
67
+ printf '{"type":"text","text":"hello"}\n' | guild agent test --ephemeral --mode json
68
+ ```
69
+
70
+ Send a single message to the agent in the current directory:
71
+
72
+ ```bash
73
+ guild agent chat --ephemeral "Hello"
74
+ ```
75
+
76
+ Save a draft version:
77
+
78
+ ```bash
79
+ guild agent save -A --message "Describe the change"
80
+ ```
81
+
82
+ Save, validate, and publish:
83
+
84
+ ```bash
85
+ guild agent save -A --message "Ready" --wait --publish
86
+ ```
87
+
88
+ ## Debugging
89
+
90
+ If a test fails or stalls:
91
+
92
+ ```bash
93
+ guild doctor
94
+ guild agent versions
95
+ guild session events <session-id>
96
+ guild session tasks <session-id>
97
+ ```
98
+
99
+ Use `--debug` when command output is too sparse:
100
+
101
+ ```bash
102
+ guild --debug agent test --ephemeral
103
+ ```
104
+
105
+ ## Agent Code Patterns
106
+
107
+ LLM agents use a prompt and tools:
108
+
109
+ ```typescript
110
+ import { llmAgent, guildTools } from '@guildai/agents-sdk';
111
+
112
+ export default llmAgent({
113
+ description: 'Helps users with Guild workspaces',
114
+ tools: { ...guildTools },
115
+ systemPrompt: `You are a helpful Guild workspace assistant.`,
116
+ mode: 'multi-turn',
117
+ });
118
+ ```
119
+
120
+ Code-first agents call tools through `task.tools`:
121
+
122
+ ```typescript
123
+ 'use agent';
124
+
125
+ import { agent, guildTools, type Task } from '@guildai/agents-sdk';
126
+ import { z } from 'zod';
127
+
128
+ const tools = { ...guildTools };
129
+ type Tools = typeof tools;
130
+
131
+ const inputSchema = z.object({
132
+ type: z.literal('text'),
133
+ text: z.string(),
134
+ });
135
+
136
+ const outputSchema = z.object({
137
+ type: z.literal('text'),
138
+ text: z.string(),
139
+ });
140
+
141
+ async function run(input: z.infer<typeof inputSchema>, task: Task<Tools>) {
142
+ await task.tools.guild_debug_log({ message: input.text });
143
+ return { type: 'text' as const, text: input.text };
144
+ }
145
+
146
+ export default agent({
147
+ description: 'Echoes user text',
148
+ inputSchema,
149
+ outputSchema,
150
+ tools,
151
+ run,
152
+ });
153
+ ```
154
+
155
+ All tool calls go through `task.tools.<toolName>(args)`.