Building a Personal AI Assistant with Claude Agent SDK and Bun

Most AI assistants are chatbots. You ask, they answer, the interaction ends. The interesting shift happening right now is treating AI as an autonomous worker — something that runs on a schedule, produces real artifacts, and delivers results without you being in the loop.

This post walks through building that kind of assistant: a background agent that runs weekly, researches a set of topics relevant to your work, and delivers a structured briefing via Telegram or email. No chatbot UI. No manual prompting. Just a scheduled process that does real work.


The Architecture

flowchart TD
    Cron[Cron schedule\nSunday 8am] --> Script[index.ts\nBun runtime]
    Script --> Memory[Read last week's report\nmemory/weekly.md]
    Script --> Agent[Claude Agent SDK\nquery loop]
    Agent --> Tools{Available tools}
    Tools --> Web[Web search]
    Tools --> Files[Read and write files]
    Tools --> API[API calls]
    Agent --> Report[Write report to\nmemory/weekly.md]
    Report --> Notify[Send via Telegram\nor email]

Three design principles make this work:

  1. Schedule-driven, not prompt-driven — runs autonomously via cron, not when you ask
  2. File-based memory — reads last week’s output before generating this week’s, avoiding repetition
  3. Real artifacts — produces a markdown report file, not just a chat response

Why Bun

Bun starts faster than Node.js — important for scheduled processes that run and exit rather than staying alive as a server. It also supports TypeScript natively without a compilation step, making the code simpler.

For a process that runs once a week, the performance difference matters less than the development experience: one file, no build step, native TypeScript.


Setting Up

curl -fsSL https://bun.sh/install | bash
mkdir my-assistant && cd my-assistant
bun init -y
bun add @anthropic-ai/claude-code

Create the project structure:

my-assistant/
├── index.ts              ← main entry point
├── memory/
│   └── weekly.md         ← previous report (auto-managed)
├── prompts/
│   └── weekly-brief.md   ← prompt template
└── .env                  ← ANTHROPIC_API_KEY, TELEGRAM_TOKEN

The Agent Loop

The Claude Agent SDK handles tool orchestration automatically. You provide tools and a prompt; the SDK manages the reasoning loop until the task is complete.

import { query } from "@anthropic-ai/claude-code";
import { readFile, writeFile } from "fs/promises";

async function runWeeklyBriefing() {
  // Load previous report to avoid repetition
  const lastReport = await readFile("memory/weekly.md", "utf-8")
    .catch(() => "No previous report.");

  // Load the prompt template
  const template = await readFile("prompts/weekly-brief.md", "utf-8");
  const today = new Date().toISOString().split("T")[0];
  const prompt = template
    .replace("{{DATE}}", today)
    .replace("{{LAST_REPORT}}", lastReport);

  let finalReport = "";

  // Run the agent
  for await (const message of query({
    prompt,
    options: {
      maxTurns: 20,
    },
  })) {
    if (message.type === "result") {
      finalReport = message.result;
    }
  }

  // Persist this week's report
  await writeFile("memory/weekly.md", finalReport);
  return finalReport;
}

The Prompt Template

The prompt is where you shape what the agent does. Keep it specific — vague prompts produce generic output.

# Weekly Briefing - {{DATE}}

You are researching DevOps and platform engineering trends for a weekly briefing.

## Last Week's Report
{{LAST_REPORT}}

## Your Task

Research and write a briefing covering:

1. **New tools or releases** — Kubernetes, Terraform, Docker, or major cloud providers
2. **Incidents worth learning from** — public post-mortems published this week
3. **Practical tips** — one concrete technique a DevOps engineer can apply immediately
4. **Community discussion** — one interesting thread from Hacker News or Reddit

Rules:
- Do not repeat topics covered in last week's report (shown above)
- Each section should be 2-3 paragraphs, not bullet lists
- Cite sources with URLs
- Write for a senior engineer — skip basics

Save the final report to memory/weekly.md before finishing.

Memory: Avoiding Repetition

The file-based memory pattern is simple but effective. Before each run, the agent reads the previous report. The prompt explicitly instructs it to avoid repeating covered topics.

This is enough for a weekly cadence. You do not need a vector database or a sophisticated memory system — a single markdown file captures enough context to produce non-repetitive output across months of runs.

// The memory pattern in three lines
const lastReport = await readFile("memory/weekly.md", "utf-8").catch(() => "");
const prompt = `Previous report:\n${lastReport}\n\nDo not repeat these topics:\n${prompt}`;
// After the run:
await writeFile("memory/weekly.md", newReport);

Delivery via Telegram

Telegram is the easiest delivery mechanism — free, API-first, and instant on mobile.

async function sendToTelegram(report: string) {
  const { TELEGRAM_TOKEN, TELEGRAM_CHAT_ID } = process.env;

  // Telegram has a 4096 character limit per message
  const chunks = report.match(/.{1,4000}/gs) ?? [report];

  for (const chunk of chunks) {
    await fetch(`https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        chat_id: TELEGRAM_CHAT_ID,
        text: chunk,
        parse_mode: "Markdown",
      }),
    });
  }
}

Get your chat ID by messaging @userinfobot on Telegram.


Scheduling with Cron

# Edit crontab
crontab -e

# Run every Sunday at 8 AM
0 8 * * 0  cd /home/user/my-assistant && bun run index.ts >> logs/assistant.log 2>&1

Create the logs directory first:

mkdir -p /home/user/my-assistant/logs

The Full Entry Point

import { runWeeklyBriefing } from "./agent";
import { sendToTelegram } from "./telegram";

async function main() {
  console.log(`[${new Date().toISOString()}] Starting weekly briefing`);

  try {
    const report = await runWeeklyBriefing();
    await sendToTelegram(report);
    console.log("Briefing delivered successfully");
  } catch (error) {
    console.error("Briefing failed:", error);
    process.exit(1);
  }
}

main();

Extending the Pattern

Once this works, the pattern extends naturally:

flowchart LR
    Base[Weekly briefing\nbase pattern] --> R1[Daily standup\nfrom git log]
    Base --> R2[Monthly cost\nreport from AWS]
    Base --> R3[Incident summary\nfrom PagerDuty]
    Base --> R4[Dependency audit\nfrom npm audit]
    Base --> R5[Team newsletter\nfrom Linear tickets]

Each variant changes the prompt template and the data source. The scheduling, memory, and delivery patterns stay the same.

The key shift in thinking: instead of asking Claude a question when you need an answer, design a workflow that delivers answers before you know you need them. The assistant does the work while you sleep and the briefing is waiting when you arrive.

Abhay

Abhay Pratap Singh

DevOps Engineer passionate about automation, cloud infrastructure, and self-hosted tools. I write about Kubernetes, Terraform, DNS, and everything in between.