Skip to main content
Coded agents are TypeScript functions you write yourself. They execute deterministically from start to finish, with no LLM driving the control flow — though you can call LLMs as needed within your code. Use coded agents when you need precise control, predictable costs, or algorithmic logic.

AutomaticallyManagedStateAgent

The recommended pattern for most coded agents. You implement an async run function, and the runtime handles state management.

Example

Add the "use agent" directive at the top of your file so the runtime can manage state between tool calls.
"use agent"

import {
  type Task,
  agent,
  pick,
  progressLogNotifyEvent,
  userInterfaceTools,
} from "@guildai/agents-sdk"
import { gitHubTools } from "@guildai-services/guildai~github"
import { z } from "zod"

const inputSchema = z.object({
  repo: z.string().describe("The GitHub repository in 'owner/name' format"),
  issue_number: z.number().describe("The issue number to summarize"),
})
type Input = z.infer<typeof inputSchema>

const outputSchema = z.object({
  summary: z.string(),
  labels: z.array(z.string()),
})
type Output = z.infer<typeof outputSchema>

const tools = {
  ...userInterfaceTools,
  ...pick(gitHubTools, [
    "github_issues_get",
    "github_issues_list_comments",
  ]),
}
type Tools = typeof tools

async function run(input: Input, task: Task<Tools>): Promise<Output> {
  const [owner, repo] = input.repo.split("/")

  await task.ui?.notify(progressLogNotifyEvent("Fetching issue..."))

  const issue = await task.tools.github_issues_get({
    owner,
    repo,
    issue_number: input.issue_number,
  })

  // Use LLM to summarize
  const result = await task.llm.generateText({
    prompt: `Summarize this GitHub issue:\n\n${issue?.body}`,
  })

  return {
    summary: result.text,
    labels: issue?.labels?.map((l) => l.name) ?? [],
  }
}

export default agent({
  description: "Summarizes a GitHub issue and extracts its labels.",
  inputSchema,
  outputSchema,
  tools,
  run,
})
The runtime only supports @guildai/agents-sdk and zod. See the SDK introduction for details.

Input and output schemas

Define your schemas using Zod. The runtime uses them to validate input and expose the agent as a typed tool for orchestrating agents.
const inputSchema = z.object({
  message: z.string().describe("The message to process"),
})

const outputSchema = z.object({
  response: z.string(),
})

Error handling

Any exception thrown from your run function is returned to the calling agent or user. Use standard TypeScript error handling:
async function run(input: Input, task: Task<Tools>): Promise<Output> {
  try {
    // ...
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error)
    throw new Error(`Failed to process: ${message}`)
  }
}

Self-managed state

SelfManagedStateAgent is an event-driven state machine for when AutomaticallyManagedStateAgent doesn’t fit — primarily parallel tool calls and custom state persistence. It’s harder to implement but has no runtime constraints. Use it when:
  • You need to make parallel tool calls
  • AutomaticallyManagedStateAgent doesn’t fit your execution model
  • You require fine-grained control over state persistence
Documentation for SelfManagedStateAgent is coming soon. The key pattern is implementing start and onToolResults callbacks instead of a single run function, with task.save() and task.restore() for state persistence between tool calls.

When to use each type

SituationRecommended type
Task can be described as a prompt + toolsllmAgent
Algorithmic, deterministic logicAutomaticallyManagedStateAgent
Need parallel tool calls or complex stateSelfManagedStateAgent
Want to minimize LLM costsAutomaticallyManagedStateAgent or SelfManagedStateAgent

Performance tips

  • LLM calls are expensive. Store the result of task.llm.generateText() in a variable if you need it more than once.
  • Environment setup is slow. Reuse Docker environments within a run rather than creating and destroying them repeatedly.
  • Batch operations. Group related API calls to reduce round-trips.
  • Use progress logs. Keep users informed during long-running operations with task.ui?.notify(progressLogNotifyEvent(...)).