Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hunvreus/heypi/llms.txt

Use this file to discover all available pages before exploring further.

This example builds a team project assistant that lives in your Discord server. It shows the middle tier of Heypi complexity—more realistic than the Telegram example but much leaner than the Slack DevOps one. The agent tracks project notes and status updates in a local Markdown file, streams replies back to Discord as they generate, and routes any status-change request through an approval gate before writing anything permanent.

What’s Included

Streaming Replies

Responses stream token-by-token into Discord as the model generates them, keeping the conversation feel snappy.

Approval-Gated Status

set_project_status requires explicit approval before appending to the notes file; the approval card shows Project, Status, and Reason.

Project Notes

project_note appends timestamped notes to state/project-notes.md instantly, no approval needed.

TypeBox Schemas

All three custom tools use @sinclair/typebox for parameter validation—the same pattern you’d use in production.

Setup

1

Clone or copy the example

cp examples/discord-project/.env.example examples/discord-project/.env
2

Install dependencies

pnpm install
3

Set environment variables

Edit examples/discord-project/.env:
DISCORD_BOT_TOKEN=<discord-bot-token>
OPENAI_API_KEY=<openai-api-key>

# Optional comma-separated allowlists.
HEYPI_DISCORD_GUILDS=
HEYPI_DISCORD_CHANNELS=
HEYPI_DISCORD_USERS=
HEYPI_APPROVERS=
Leave the allowlists empty to accept every event Discord delivers. Set comma-separated IDs to restrict which guilds, channels, or users may trigger the agent.
4

Enable Message Content Intent

In the Discord Developer Portal, open your application and go to Bot → Privileged Gateway Intents. Enable Message Content Intent. Without it, the bot cannot read message text.
5

Invite the bot to your server

Generate an invite URL and check your token:
pnpm heypi discord check --env examples/discord-project/.env
Then invite with the required scopes:
pnpm exec heypi discord invite --client-id <your-client-id>
Use pnpm heypi discord channels or pnpm heypi discord observe to find IDs for HEYPI_DISCORD_GUILDS, HEYPI_DISCORD_CHANNELS, and HEYPI_APPROVERS.
6

Run the agent

pnpm run dev:discord
# or directly:
node index.js
Mention the bot in any allowed channel to start: @heypi help.

Configuration

const app = createHeypi({
  state: { root: stateRoot },
  adapters: [
    discord({
      token: required("DISCORD_BOT_TOKEN"),
      allow: {
        guilds: list("HEYPI_DISCORD_GUILDS"),
        channels: list("HEYPI_DISCORD_CHANNELS"),
        users: list("HEYPI_DISCORD_USERS"),
      },
      trigger: "mention",
      streaming: true,
    }),
  ],
  agent: agentFrom("./agent", {
    model: "openai/gpt-5-mini",
    tools: [...coreTools(), projectNote, setProjectStatus, readProjectNotes],
  }),
  approval: {
    approvers: list("HEYPI_APPROVERS"),
    expiresInMs: 10 * 60 * 1000,
  },
  runtime: { root: workspace("./workspace") },
});
streaming: true enables token-by-token streaming into Discord. approval.expiresInMs sets a 10-minute window for pending approvals before they expire automatically.

Custom Tools

All three tools are defined inline in index.ts using tool() from @hunvreus/heypi and TypeBox parameter schemas.

project_note — Append a Note (No Approval)

Appends a timestamped note to state/project-notes.md. Runs immediately with no confirmation step:
const projectNote = tool<{ note: string; project?: string }>({
  name: "project_note",
  description: "Append a short project note to the local project log.",
  parameters: Type.Object({
    note: Type.String({ description: "Concise note to save." }),
    project: Type.Optional(Type.String({ description: "Project or workstream name." })),
  }),
  execute: async ({ note, project }) => {
    await mkdir(dirname(notesPath), { recursive: true });
    const prefix = project ? `[${project}] ` : "";
    await appendFile(notesPath, `- ${new Date().toISOString()} ${prefix}${note}\n`, "utf8");
    return "project note saved";
  },
});

set_project_status — Status Update (Approval Required)

Appends a status change to state/project-notes.md but only after an approver confirms the details. The confirm function builds the approval card:
const setProjectStatus = tool<{ project: string; status: string; reason: string }>({
  name: "set_project_status",
  description: "Append an approved project status update.",
  parameters: Type.Object({
    project: Type.String({ description: "Project or workstream name." }),
    status: Type.String({ description: "New status, e.g. on track, blocked, at risk, shipped." }),
    reason: Type.String({ description: "Short reason for the status change." }),
  }),
  confirm: ({ project, status, reason }) => ({
    message: "Update project status.",
    details: [
      { label: "Project", value: String(project) },
      { label: "Status", value: String(status) },
      { label: "Reason", value: String(reason) },
    ],
  }),
  execute: async ({ project, status, reason }) => {
    await mkdir(dirname(notesPath), { recursive: true });
    await appendFile(
      notesPath,
      `- ${new Date().toISOString()} [${project}] status=${status}; ${reason}\n`,
      "utf8"
    );
    return `status updated: ${project} is ${status}`;
  },
});
The confirm function returning an object (not false) is what triggers the approval gate. Heypi surfaces the message and details fields as a structured card in Discord. The agent does not ask for confirmation in chat—the platform adapter handles the interaction.

read_project_notes — Read Saved Notes

Returns the full contents of state/project-notes.md, or a friendly message if no notes have been saved yet:
const readProjectNotes = tool({
  name: "read_project_notes",
  description: "Read saved project notes.",
  parameters: Type.Object({}),
  execute: async () => {
    try {
      return await readFile(notesPath, "utf8");
    } catch {
      return "No project notes yet.";
    }
  },
});

Agent Folder

agent/
  SOUL.md     # Persona: direct, practical, careful with team project state
  AGENTS.md   # Operating guidance: when to call each tool, keep replies short
SOUL.md sets a one-line persona: “You are direct, practical, and careful with team project state.” AGENTS.md tells the agent to call project_note when asked to record a note, call set_project_status with the project, new status, and a short reason when asked to change status, and never ask the user to confirm in chat—the app handles the gate for the concrete update.

Try It Out

@bot note that frontend polish is blocking the beta
@bot set the mobile-beta status to blocked because design QA found layout regressions
@bot summarize current project notes
The second message triggers an approval card in Discord. An approver (anyone with the bot if HEYPI_APPROVERS is empty, or only listed user IDs if set) must click Approve before the status is written.

Build docs developers (and LLMs) love