Skip to main content
An llmAgent pairs a system prompt with a tool set. You define what the agent knows and what it can do — the LLM figures out how to do it. It is the TypeScript form of a prompt-driven agent: no agent logic to write, but an Agent SDK package you can extend. Native agents are LLM-powered too. The names distinguish how you author and run them: a Native agent is Markdown executed by Guild’s managed agent loop, while a TypeScript LLM agent uses the llmAgent Agent SDK template.
For a prompt-driven agent, try a Native agent first — it is the same idea with no code and no container, written as PROMPT.md plus a guild.yaml tool list. Build an llmAgent when you need something Native does not cover: attachments, a custom input schema, per-agent model preferences, or asking the user to connect an integration.

Example

The description field is optional and deprecated as of @guildai/agents-sdk 0.4.0. Guild generates the agent’s published description automatically from its code, so setting description no longer affects the published description.

Input and output

By default, an llmAgent takes text in and returns text out:
The output schema is fixed. The input schema is a default you can replace — see Structured input.

Attachments

Pass images or PDFs alongside a turn through the optional attachments array. Each attachment carries its bytes inline as base64, so the agent sandbox never has to fetch anything over the network:
llmAgent expands each attachment into a data: URL and adds it to the user message as the matching content part. A content_type beginning with image/ becomes an ImagePart; a content_type of exactly application/pdf becomes a FilePart.
Any other content_type is ignored. The attachment is dropped from the message rather than raising an error, as is any attachment with empty data.
Because each attachment becomes an ordinary data: URL string, it survives state persistence unchanged and is preserved when the agent resumes in a multi-turn session. Attachments can also arrive from tool results. When a tool returns an attachments array, llmAgent strips those bytes out of the tool result content and sends them as a separate user message, so the model can see the images without the base64 bloating the tool transcript.

Structured input

Pass inputSchema to accept a typed object instead of a bare string, and inputTemplate to say how that object becomes the first user message. The template is mustache-style: {{field}}, or a dotted path for a nested value. A field the input does not carry renders as an empty string.
Omit both and the defaults apply: the input shape above, rendered with {{text}}.
inputSchema must be z.object({ ... }) at the root. The runtime registers each agent as an LLM tool, and LLM providers require "type": "object" at the top of tool input schemas. Root schemas that are not objects — such as z.discriminatedUnion(), z.union(), z.record(), or a primitive like z.string() — are rejected and fail the build. Wrap a non-object schema in a root z.object() property instead.

Execution modes

llmAgent supports two modes:
'one-shot' | 'multi-turn'
default:"'one-shot'"
  • "one-shot" — The agent processes the input, returns a single response, then terminates.
  • "multi-turn" — The agent continues interacting with the user until it calls the __submit__ tool to signal task completion.

The __submit__ tool

In "multi-turn" mode, the runtime injects a __submit__ tool that the LLM must call to end the session. You do not declare it in your tools set.
  • Purpose — Signals that the agent is done. The value the LLM passes to __submit__ becomes the agent’s final output. Earlier turns in the session are not returned to the caller.
  • Parallel calls__submit__ must not be called in the same turn as any other tool. The runtime injects this rule into the system prompt; repeat it in your own systemPrompt if the model tries to submit while still calling tools.
  • Completion — Until the LLM calls __submit__, the session stays open for follow-up messages.

Pre-tool text visibility

When an llmAgent emits text before calling a tool, the runtime preserves it as a continued response stream. By default this text is shown to the user as user-facing progress.
'visible' | 'hidden'
default:"'visible'"
  • "visible" (default) — Text emitted before a tool call is shown to the user as a continued response stream.
  • "hidden" — Pre-tool text is not surfaced. Use this when the agent uses pre-tool text for internal reasoning rather than user-facing progress.

Tool recommendations

  • ui_notify is wired internally so progress notifications (task.ui.notify()) work, but it is hidden from the model unless you explicitly add ui_notify to the agent’s tools. Add userInterfaceTools if the agent needs ui_prompt or ui_ping.
  • Include guildTools if the agent uses tools that require authorization (e.g., GitHub access), so it can request credentials when needed.

Selecting specific tools

Use pick to include only the tools you need. See Selecting specific tools in the SDK reference.

LLM preferences

By default, Guild resolves the provider and model from the workspace owner’s LLM settings. An llmAgent can express an ordered list of preferred providers — and, optionally, models — with llmPreferences:
Earlier entries take priority over later ones. Omit model to let the server choose a model from the matching policy. Preferences are strict, and the account’s model policies have the final say: a preferred model is used only if a policy allows it. If no listed preference is allowed — for example, the account has no key for any preferred provider — the agent’s LLM calls fail with an error naming the refused preferences, rather than silently running on the account’s default model. Declare preferences only when the agent genuinely requires specific models; an agent without llmPreferences always uses the account’s default configuration. For coded and self-managed agents, pass llmPreferences per call to task.llm.generateText instead. See LLM preferences for that form and the full LLMPreference shape.

When to use TypeScript LLM agents