---
title: "Step 7: Team playbooks"
description: "Load the caller's team playbook with a dynamic skill keyed on the principal."
---

The glossary from [Step 6](./remember-definitions) is per-session. But your teams have standing analysis conventions (Growth runs cohort retention a particular way, Finance has its own revenue-recognition rules), 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 actually 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 we 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, team included. 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 retention, use weekly cohorts anchored on signup week, " +
      "report curves not point estimates, and exclude trial accounts.",
  },
  finance: {
    title: "Finance analysis playbook",
    markdown:
      "Report revenue net of refunds and recognized over the subscription term. " +
      "Always reconcile against the close-of-month snapshot.",
  },
};

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 a single time, and the resulting skill stays available for every turn that follows. Returning `null` produces no skill at all, so a caller with no team simply gets no playbook.

## See it route

A Growth user asks "what's our 8-week retention?" The model sees the playbook fits, calls `load_skill`, and applies the Growth conventions to that turn: weekly cohorts, no trial accounts. Ask the same question as a Finance user and you'll never see the Growth playbook. You get Finance's.

The team comes from authenticated claims, not from the message, so one tenant can't borrow another's playbook by asking nicely. (Those `attributes` are set by the auth layer in [Step 9](./ship-it).)

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

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

Depth: [Skills](../skills) · [Dynamic capabilities](../advanced/dynamic-capabilities)
