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

# Agents

> Workspace-defined automations that run tasks, hold long-lived sessions against the runtime, and pause for human approval at sensitive steps.

export const PageMeta = ({audience, readingTime, prereqs, level}) => {
  const items = [audience && ({
    key: "audience",
    label: "AUDIENCE",
    value: audience
  }), readingTime && ({
    key: "time",
    label: "READ",
    value: readingTime
  }), level && ({
    key: "level",
    label: "LEVEL",
    value: level
  }), prereqs && prereqs.length > 0 && ({
    key: "prereqs",
    label: "PREREQS",
    value: prereqs.join(" · ")
  })].filter(Boolean);
  if (items.length === 0) return null;
  return <div className="gv-page-meta" role="group" aria-label="Page metadata">
      {items.map(item => <div className="gv-page-meta__item" key={item.key}>
          <span className="gv-page-meta__label">{item.label}</span>
          <span className="gv-page-meta__value">{item.value}</span>
        </div>)}
    </div>;
};

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const FeatureTable = ({columns, rows, featuredRow, caption, variant}) => <div className={`gv-feature-table ${variant ? `gv-feature-table--${variant}` : ""}`}>
    <table>
      {caption && <caption className="gv-feature-table__caption">{caption}</caption>}
      <thead>
        <tr>
          {columns.map(col => <th key={col.key} scope="col">
              <span className="gv-feature-table__header">{col.label}</span>
            </th>)}
        </tr>
      </thead>
      <tbody>
        {rows.map((row, i) => {
  const rowClass = [i === featuredRow ? "gv-feature-table__row--featured" : "", row.chosen ? "gv-feature-table__row--chosen" : ""].filter(Boolean).join(" ");
  return <tr key={i} className={rowClass}>
              {columns.map(col => {
    const v = row[col.key];
    if (variant === "tradeoff" && Array.isArray(v)) {
      const pill = col.key === "pros" ? "gv-feature-table__pill--pro" : col.key === "cons" ? "gv-feature-table__pill--con" : "";
      return <td key={col.key}>
                      {v.map((item, j) => <span key={j} className={`gv-feature-table__pill ${pill}`}>{item}</span>)}
                    </td>;
    }
    return <td key={col.key}>{v}</td>;
  })}
            </tr>;
})}
      </tbody>
    </table>
  </div>;

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="CONCEPT" />

<PageMeta audience="Developers, AI integrators" readingTime="7 min" prereqs={["Capabilities"]} level="Concepts" />

Most of what a gavAI workspace does is request-shaped — a visitor hits a page, a handler runs, a response goes back. Agents are the part of the platform that runs without a request behind it. An agent is a workspace-defined automation that the runtime drives on a schedule or on a trigger, holds a long-lived session for, and pauses for human sign-off at sensitive steps. By the end of this page you will know what an agent is, how it differs from a webhook handler, what it can and cannot reach, and how approval gates fit in.

## A first agent

Conceptually, an agent is a name plus a recipe. The name is how operators identify it in the console and the API; the recipe is the ordered set of steps it takes when it runs. A minimal one looks like this:

```json agent.json theme={null}
{
  "name": "Weekly digest",
  "trigger": { "kind": "schedule", "cron": "0 9 * * 1" },
  "steps": [
    { "read": "runQuery",  "args": { "table": "orders", "since": "7d" } },
    { "read": "computed",  "args": { "expression": "summarize(rows)" } },
    { "write": "sendEmail","args": { "template": "weekly-digest" } }
  ]
}
```

Three things are happening. A schedule trigger fires the run; the run performs ordered steps; each step is either a read (against the workspace's data or a computed expression) or a write (a capability invocation with the steps above as input). The runtime owns the session, the audit trail, and the failure handling — the recipe just declares the shape of the work.

## The mental model: scheduled session

Think of an agent as a **scheduled session against the runtime**. When the trigger fires, the platform opens a session for the agent, authenticates it as a workspace-scoped principal, and feeds it the steps in order. Each step runs inside the same isolation boundary as a normal page request — the agent reads from the same workspace database, dispatches the same capabilities, hits the same rate limits.

```mermaid theme={null}
sequenceDiagram
    participant T as Trigger
    participant R as Runtime
    participant C as Capabilities
    participant A as Audit log
    T->>R: schedule fires
    R->>R: open agent session
    R->>C: step 1 (read)
    C-->>R: rows
    R->>A: append audit entry
    R->>C: step 2 (read)
    C-->>R: computed value
    R->>A: append audit entry
    R->>C: step 3 (write — needs approval)
    R-->>R: create approval record, pause
    Note over R: ... operator approves ...
    R->>C: dispatch write
    C-->>R: result
    R->>A: append audit entry
    R-->>T: run complete
```

Two properties fall out of that shape:

* **An agent has no privileges beyond the workspace.** It cannot reach another workspace's data; it cannot mint a token for itself; it cannot bypass policy. The same boundaries that hold for a human caller hold for the agent's session.
* **Every step is auditable.** Reads and writes both append to the workspace's audit log under the agent's principal. The log records what the agent looked at, not only what it changed.

## What an agent can do

An agent's recipe is built from a small set of *agent capabilities*. They split into reads (gather context without changing state) and writes (dispatch a side effect or change state).

<FeatureTable
  caption="The agent capability surface"
  columns={[
{ key: "kind", label: "Kind" },
{ key: "name", label: "Capability" },
{ key: "what", label: "What it does" }
]}
  rows={[
{ kind: "read", name: "Read database", what: "Pull rows from a workspace schema table with a typed filter." },
{ kind: "read", name: "Read computed", what: "Evaluate a computed expression against the current step's context." },
{ kind: "read", name: "Read external", what: "Fetch a resource from an allow-listed external URL through the workspace's outbound proxy." },
{ kind: "read", name: "Read client", what: "Read the rendered state of a page block the agent is acting alongside." },
{ kind: "read", name: "Read server secret", what: "Resolve a workspace secret by its declared purpose — value never returned, only used for the next dispatch." },
{ kind: "write", name: "Trigger capability", what: "Dispatch a runtime capability (sendEmail, collectPayment, storeFile, …) with structured arguments." },
{ kind: "write", name: "Trigger workflow", what: "Kick off a named workflow run, optionally with input arguments." },
{ kind: "write", name: "Write client", what: "Push a state update or UI hint to a connected page block." },
{ kind: "write", name: "Write agent-provided", what: "Persist an agent-authored record back into the workspace under an agent-scoped namespace." }
]}
/>

The split between reads and writes is the basis for the approval system below — writes are the steps that get gated.

## Triggers

An agent fires on a trigger. Triggers are declarative and live on the agent definition; you do not write listener code.

<DefList>
  <Def term="schedule">Cron-style schedule expression evaluated in the workspace's configured timezone.</Def>
  <Def term="event">An event family from the platform's event stream — `forms.submitted`, `payments.succeeded`, and the others listed in the [event catalogue](/webhooks/events).</Def>
  <Def term="manual">Operator-initiated. The agent is dormant until a human starts a run from the console or the API.</Def>
  <Def term="approval_followup">Implicit — fires when an approval the agent created earlier is decided. The next steps run inside the original session.</Def>
</DefList>

A single agent can declare multiple triggers. The runtime opens a separate session per fired trigger, so two concurrent triggers do not race against each other's state.

## Sessions, runs, and replay

A *run* is one execution of the agent's recipe. The runtime gives each run a stable id, a session token scoped to that run, and a position cursor that advances as steps complete. If the session is disconnected mid-run — a deploy, a transient network failure — the run resumes from the cursor on reconnect rather than restarting from step one. Steps that already wrote are not re-dispatched; the runtime knows because the audit chain already recorded the result.

This matters for two reasons:

<ArrowList>
  <ArrowItem>**Long-running agents are safe.** A run that spans hours of operator review at an approval gate does not lose progress when infrastructure churns underneath it.</ArrowItem>
  <ArrowItem>**Replay is a debugging tool.** Operators can fetch a finished run, replay the read steps against current state, and see how the agent would behave today without dispatching any writes. Useful for diagnosing why an agent made a decision a week ago.</ArrowItem>
</ArrowList>

## Approval gates

Writes can be gated. A step marked `requires_approval` does not dispatch when the run reaches it — instead, the runtime opens an [approval record](/concepts/approvals), pauses the session, and waits. An operator approves or denies; the run continues or stops accordingly.

The decision is recorded in the run's audit chain alongside the principal who made it. That is the surface a workspace owner uses to answer "did anyone authorize this?" after the fact.

## When to use an agent (and when not to)

Use an agent when:

<ArrowList>
  <ArrowItem>The work is **periodic** (a nightly report) or **event-shaped** (handle the submission stream from a high-volume form). Both fit the trigger model cleanly.</ArrowItem>
  <ArrowItem>The work needs **stateful progress** across multiple capability calls — gather context, decide, then dispatch. A webhook handler that has to re-fetch context on every retry is doing this manually.</ArrowItem>
  <ArrowItem>A step needs **human review** before it dispatches. Agents are the only surface where the platform owns the approval gate; everywhere else you would build the queue yourself.</ArrowItem>
</ArrowList>

Don't use an agent when:

<ArrowList>
  <ArrowItem>The work is **request-shaped**. A capability call on a page submission is simpler — let the page invoke the capability directly.</ArrowItem>
  <ArrowItem>The work is **outside the workspace**. Agents cannot reach across workspaces; orchestration that needs to coordinate two workspaces belongs in your own code, calling the API as two separate clients.</ArrowItem>
  <ArrowItem>The work is **fire-and-forget with no review**. A webhook subscription is the simpler surface — agents pay for the session and audit overhead that you don't need in that case.</ArrowItem>
</ArrowList>

## Related

<Columns cols={2}>
  <BrandCard eyebrow="CONCEPT" title="Workflows" href="/concepts/workflows" icon="workflow">
    The action-graph surface agents invoke for multi-step writes.
  </BrandCard>

  <BrandCard eyebrow="CONCEPT" title="Approval gates" href="/concepts/approvals" icon="user-check">
    How agents pause for human sign-off and how policy decides which steps need it.
  </BrandCard>

  <BrandCard eyebrow="REFERENCE" title="Agents API" href="/api-reference/resources/agents" icon="bot">
    Endpoints to create, activate, deactivate, and inspect agents and their runs.
  </BrandCard>

  <BrandCard eyebrow="EVENTS" title="Event catalogue" href="/webhooks/events" icon="list">
    The event families an agent can trigger off of.
  </BrandCard>
</Columns>
