> ## Documentation Index
> Fetch the complete documentation index at: https://docs.guild.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Auto-managed state agents

> Build deterministic TypeScript agents with automatic state management.

<iframe src="https://www.youtube.com/embed/2F2ZDnYgQAw" title="YouTube video player" frameborder="0" className="w-full aspect-video rounded-xl" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen />

Auto-managed state agents (using `AutomaticallyManagedStateAgent`) 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.

The runtime handles state persistence automatically. You write a straightforward `async run` function, and the [babel-plugin-agent-compiler](/packages/babel-plugin) transforms it into a resumable state machine behind the scenes.

## Example

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

```typescript theme={null}
"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,
})
```

<Warning>
  The runtime only supports `@guildai/agents-sdk` and `zod`. See the [SDK introduction](/guide/sdk-introduction) for details.
</Warning>

<Note>
  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.
</Note>

## 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.

<Warning>
  **`inputSchema` must use `z.object()` at the root.** Do not use union or discriminated-union root schemas. LLM providers require `"type": "object"` at the top of input schemas.
</Warning>

```typescript theme={null}
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:

```typescript theme={null}
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}`)
  }
}
```

## Limitations

The `"use agent"` directive relies on the [Babel compiler](/packages/babel-plugin) to transform your code into a state machine. This means:

* **No `Promise.all`, `Promise.any`, or `Promise.race`** — awaiting tool calls one at a time serializes between the calls, so each runs independently. If the calls are independent tool calls and you want them to run **in parallel** rather than one after another, use `task.gather` / `task.gatherSettled` instead — see [Parallel tool calls with `task.gather`](#parallel-tool-calls-with-taskgather) below. These are the compiler-supported replacement for `Promise.all` / `Promise.allSettled` when every input is a tool call.
* **No dynamic function references across `await` points** — conditionally assigned functions may not survive serialization
* **No external imports** — only `@guildai/agents-sdk`, `zod`, and `@guildai-services/*` are supported

### Compilation required for sub-agents and service hooks

Calling a sub-agent or service hook from an uncompiled auto-managed state agent crashes at runtime. To prevent this, Guild validates at build time that any auto-managed state agent using these tools is compiled.

An auto-managed state agent must be compiled with the `"use agent"` directive when it registers or calls:

* Sub-agents (tools of `toolType: "agent"`).
* Individually registered integration service hooks.

Bundled platform hook services — such as `ui`, `guild`, and `console` — are exempt and do not require compilation.

<Warning>
  Saving or publishing an uncompiled auto-managed state agent that registers sub-agents or integration service hooks fails with a build validation error, which blocks the publish. Add the `"use agent"` directive to the top of the agent's main module to compile it, or remove those tools.
</Warning>

## Parallel tool calls with `task.gather`

`Promise.all` does not survive serialization (see [Limitations](#limitations) above). For auto-managed state agents (`"use agent"`), the supported way to fan out **independent tool calls concurrently** is `task.gather` and `task.gatherSettled`. The runtime allocates every subtask atomically, dispatches them as a single batch, suspends the state machine while they run, and assembles the results in source order on resume.

### `task.gather` — fail fast

Analogous to `Promise.all`: resolves to an array of results in source order, or rejects on the first failure.

```typescript theme={null}
"use agent"

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

const tools = {
  ...pick(gitHubTools, ["github_pulls_list", "github_issues_list_for_repo"]),
}
type Tools = typeof tools

async function run(input: Input, task: Task<Tools>): Promise<Output> {
  const [pulls, issues] = await task.gather([
    task.tools.github_pulls_list({ owner, repo, state: "open" }),
    task.tools.github_issues_list_for_repo({ owner, repo, state: "open" }),
  ])

  return {
    type: "text",
    text: `${pulls.length} open PRs, ${issues.length} open issues`,
  }
}
```

Results are fully typed: `task.gather([a, b])` returns a tuple whose elements are the awaited return types of `a` and `b`, in order.

### `task.gatherSettled` — collect every outcome

Analogous to `Promise.allSettled`: never rejects. Each entry is a `PromiseSettledResult` describing whether that call fulfilled or rejected, so one failing call doesn't sink the rest.

```typescript theme={null}
const results = await task.gatherSettled([
  task.tools.github_repos_get({ owner, repo: "a" }),
  task.tools.github_repos_get({ owner, repo: "b" }),
])

for (const result of results) {
  if (result.status === "fulfilled") {
    task.console.info(`got ${result.value.full_name}`)
  } else {
    task.console.warn(`failed: ${result.reason}`)
  }
}
```

### Rules and limits

* **Inputs must be tool-call expressions** — `task.tools.X(...)`, sub-agent tool calls, or hook tools. The `ToolCallPromise` brand on the parameter type rejects arbitrary promises (`fetch(...)`, timers, library code) at compile time. This is the deliberate boundary that makes `gather` work where `Promise.all` can't: every input is a dispatchable tool call the runtime can serialize, not an opaque promise.
* **Use only from a compiled (`"use agent"`) agent body.** Self-managed state agents already fan out in parallel by returning `callTools([...])` with more than one entry — they don't need `gather`.

## When to use auto-managed state

| Situation                        | Use auto-managed?                                               |
| -------------------------------- | --------------------------------------------------------------- |
| Algorithmic, sequential logic    | Yes                                                             |
| You want minimal boilerplate     | Yes                                                             |
| You need parallel tool calls     | Yes — use [`task.gather`](#parallel-tool-calls-with-taskgather) |
| You need full control over state | No — use [self-managed state](/guide/self-managed-agents)       |
