---
title: Team Playbooks
description: Part 6 of the Build an Agent tutorial. Load the caller's team playbook with a dynamic skill keyed on the principal.
---

# Team Playbooks



The glossary from [Step 5](./remember-definitions) is per-session. But your teams have standing analysis conventions for the analytics assistant (Growth groups revenue by date, Finance reconciles customer totals), and those shouldn't bleed across tenants. Load the right team's playbook for whoever is asking.

A skill is an on-demand procedure. The model pulls it in with `load_skill` only when a turn needs it. Make it dynamic and the skill gets decided at runtime instead of baked in. A `defineDynamic` resolver reads the session and returns a `defineSkill` (or nothing). Here you key that decision on the caller's identity in `ctx.session.auth`.

## A playbook per principal

`ctx.session.auth.current` holds the most recent caller, or `null` if there isn't one. Its `attributes` are the claims your auth layer stamped on, including the team. Read the team, look up that team's playbook, and emit a skill for it:

```ts title="agent/skills/team-playbook.ts"
import { defineDynamic, defineSkill } from "eve/skills";

const PLAYBOOKS: Record<string, { title: string; markdown: string }> = {
  growth: {
    title: "Growth analysis playbook",
    markdown:
      "When analyzing sample orders, group revenue by order date, report dollars, " +
      "and compare customers by plan.",
  },
  finance: {
    title: "Finance analysis playbook",
    markdown:
      "Reconcile total order revenue against the daily and customer totals. " +
      "Report dollars and label this as gross order revenue; the dataset has no refunds.",
  },
};

export default defineDynamic({
  events: {
    "session.started": async (_event, ctx) => {
      const team = ctx.session.auth.current?.attributes.team;
      const key = Array.isArray(team) ? team[0] : team;
      const playbook = key ? PLAYBOOKS[key] : undefined;
      if (!playbook) return null;

      return defineSkill({
        description:
          `Use when answering analysis questions for the ${key} team. ` +
          `Contains that team's standing conventions.`,
        markdown: `# ${playbook.title}\n\n${playbook.markdown}`,
      });
    },
  },
});
```

`session.started` fires once per session. The resolver reads the team once, and the resulting skill stays available for every turn that follows. Returning `null` produces no skill, so a caller with no team gets no playbook.

## See it route

The team comes from authenticated claims, which the auth layer stamps on in [Step 8](./ship-it). Until then `ctx.session.auth.current` has no `team`, so the resolver returns `null` and no playbook loads. To verify routing now, stamp a team in local dev. Add a dev-only entry to `agent/channels/eve.ts` ahead of `localDev()`, and remove it before Step 8 wires real auth:

```ts title="agent/channels/eve.ts"
import { eveChannel } from "eve/channels/eve";
import { localDev, placeholderAuth, vercelOidc, type AuthFn } from "eve/channels/auth";

// Dev-only: stamp a team so Step 6's playbook resolver has something to read.
// Remove before Step 8.
const devTeam: AuthFn<Request> = () =>
  process.env.NODE_ENV === "production"
    ? null
    : {
        attributes: { team: "growth" },
        authenticator: "dev-team",
        principalId: "dev",
        principalType: "user",
      };

export default eveChannel({
  auth: [devTeam, vercelOidc(), localDev(), placeholderAuth()],
});
```

Restart with `npm run dev` and ask "Summarize May 2026 revenue using our team's playbook." The model calls `load_skill`, queries the sample data, and applies Growth's daily and plan groupings. Switch `team` to `"finance"`, restart, and start a new session before asking again. The resolver runs at `session.started`, so changing the team does not replace the playbook in an existing session. The same question in the new session uses Finance's reconciliation rules.

Because the team comes from authenticated claims, not from the message, one tenant can't borrow another's playbook through the message content.

The same `defineDynamic` resolver drives dynamic tools and instructions too. For the full mechanism, see [Dynamic capabilities](../guides/dynamic-capabilities).

→ Next: [Guard the spend](./guard-the-spend)

Learn more: [Skills](../skills) · [Dynamic capabilities](../guides/dynamic-capabilities)


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)