Picks and Shovels: tech marketing for the AI era.

Get your copy →
|10m read

I built an AI agent platform. Here's how it works.

An Agent Server, a macOS menu bar app, an iOS app, a monitoring dashboard. All built on Claude Code and the Agent SDK. Here's every piece and what it does.

I built an AI agent platform. Here's how it works.

I spent a weekend building an AI agent platform that would automate the marketing frameworks in my book Picks and Shovels. By Sunday night I had an Agent Server running agents on cron schedules, a macOS menu bar app keeping it alive, an iOS app for monitoring on the go, and Agent Panel, a web dashboard showing every agent run in real time. About 2,500 lines of TypeScript for the server. Native Swift apps. A Next.js dashboard with WebSocket updates.

The whole thing runs on Claude Code and the Agent SDK. I've written before about how I use Claude to automate developer marketing and Agent Skills for developer marketing workflows. This is the next step: agents that run while I sleep.

I'm still learning. I'm sure there are better ways to do half of what I've built here. But it works, I use it every day, and I figured I'd share what I've got so far.

Here's what I built:

  • Agent Server -- A Node.js/TypeScript process that discovers agent definitions (YAML or Markdown files), evaluates cron schedules, acquires file locks, and runs agents via the Claude Agent SDK. About 2,500 lines of code. It's the hub that connects everything else.
  • Agent Server macOS menu bar app -- A native Swift/SwiftUI app that keeps the Agent Server running persistently, shows active run status, and provides a built-in editor for agent definitions.
  • Agent Panel -- Agent observability system with a dashboard that shows every agent run in real time: status, duration, token usage, cost, tools used, and the full activity timeline. Also has a native iOS app with full feature parity. Powered by Supabase.
  • Telegram bot -- Sends notifications when agents complete, routes on-demand requests to the right agent, and handles interactive workflows where an agent needs human input mid-run.

Here's how the pieces fit together:

The Agent Server is the center of gravity. It talks to Claude Code to run agents, reports telemetry to Agent Panel for monitoring, and communicates with Telegram for notifications and interactive workflows.

The Agent Server macOS menu bar app gives you a quick way to see which agents are running and their status. (But you can also run it from the command line.)

The macOS menu bar app showing agent definitions

The macOS app showing a live agent run with activity timeline

The Agent Server is the brains of the outfit

The Agent Server does the actual work. It's a Node.js process that discovers agent definitions, evaluates cron schedules, acquires file locks, runs Claude Code via the Agent SDK, and reports telemetry back to Agent Panel. TypeScript, strict mode, ESM.

You define agents as YAML files or Markdown files with YAML frontmatter. Here's one of mine:

yaml
yaml
id: daily-focus
name: Daily Focus List
schedule: "0 5 * * 2-6"
timezone: Europe/Lisbon
tools:
  - Read
  - Bash
max_turns: 30
prompt: |
  You are a daily planning agent. Search Slack, Linear, and Notion
  for activity in the last 24 hours. Build a prioritized focus list.

For longer prompts, I switched to Markdown files. YAML frontmatter for config, Markdown body for the prompt. You get proper formatting, headers, code blocks, numbered steps. My weekly status report prompt is 170 lines. YAML would have been miserable for that.

Under the hood, the Agent SDK is the same engine that powers Claude Code. When you run an agent, it gets the full Claude Code environment: MCP servers, file system access, tool use. A single query() call with a prompt, max turns, and allowed tools gives you a full agentic loop. I went with the SDK over raw API calls because agents need tool use, multi-turn conversation, and permission management, and the SDK handles all of that out of the box.

How do I create my first AI agent?

If you want to try it yourself, here's how to get started:

bash
bash
git clone https://github.com/coolasspuppy/agent-server.git
cd agent-server/server-app
npm install && npm run build
agent-server init      # creates ~/.agent-server/ with a sample agent
agent-server start     # starts the server

The init command drops a sample agent at ~/.agent-server/agents/hello-world.yaml. The server picks it up automatically. Edit that file or create new ones in the same directory. Anything ending in .yaml, .yml, or .md in that folder gets discovered as an agent.

The simplest possible agent:

yaml
yaml
id: hello
name: Hello World
schedule: "*/5 * * * *"
timezone: America/New_York
tools:
  - Bash
max_turns: 5
prompt: |
  Say hello and report the current time.

And the Markdown format for when your prompts get longer:

markdown
markdown
---
id: weekly-report
name: Weekly Priority Report
schedule: "0 5 * * 1"
timezone: America/New_York
tools:
  - Read
  - Write
  - Bash
max_turns: 30
---
 
Search Slack, Linear, and Notion for this week's activity.
Group accomplishments by project. Create a Notion page.

You can also trigger agents manually, which I do all the time while I'm iterating on a new prompt:

bash
bash
agent-server list                              # show all discovered agents
agent-server run hello                         # run immediately
agent-server run hello --with "extra context"  # run with additional context

Running agents on a schedule

The server runs a timer loop, checking every 60 seconds by default. Each tick, it:

  1. Discovers all agent files in the agents directory
  2. Evaluates each agent's cron expression against the current time
  3. Tries to acquire a PID-based file lock (so the same agent can't run twice simultaneously)
  4. Calls query() from the Agent SDK with the agent's prompt and options
  5. Streams events back for logging and telemetry
  6. Releases the lock in a finally block

One thing I ran into early: my laptop sleeps. Agents miss their windows. So I added sleep/wake catch-up. Set AGENT_SERVER_CATCH_UP=true and any agents that missed their cron window during sleep will fire immediately when the machine wakes up. My daily focus agent runs at 5am. If my laptop was asleep at 5am and I open it at 7am, it catches up right away.

Beyond cron schedules, agents can also watch file system paths:

yaml
yaml
watch:
  - path: "~/output"
    glob: "*.md"

When a matching file changes, the agent fires. I use this for agents that should react to local changes rather than run on a timer. Still experimenting with how useful this is in practice.

Restricting agent permissions

This is the part I thought hardest about. An agent that can read your Slack should not be able to post to Slack. An agent that creates Notion pages should not be able to delete them. If you're going to run agents unattended, you need guardrails.

The permission system uses glob-based patterns. You define allow and deny lists. Deny always wins over allow.

yaml
yaml
permissions:
  allow:
    - "mcp__claude_ai_Slack__slack_read_*"
    - "mcp__claude_ai_Slack__slack_search_*"
  deny:
    - "mcp__claude_ai_Slack__slack_send_*"
    - "mcp__claude_ai_Slack__slack_create_*"

When an agent defines permissions, the server builds a canUseTool callback that gets passed to the SDK. The SDK calls it before every tool invocation. If the agent tries to use a tool outside its allow list, the SDK blocks it.

I think about two phases to agents. The first phase involves going out and fetching information and organizing it. Most of my agents stop here. After gathering data, they put it somewhere that I can read it (my Notion, a Telegram message, etc.) The second phase is agents that act more "exotically" on that information: maybe they post somewhere as me, maybe they book a restaurant reservation, etc. Actions that are more risky. I won't build these kinds of agents for a little while, until I'm confident in the first phase.

In practice, this means my daily focus agent can search Slack for messages but can never send one. My artifact tracker can create Notion pages in one specific database but can never modify existing pages. My restaurant checker can read Google Maps but cannot make a booking.

I'm sure there are edge cases I haven't thought of yet. But the principle of least privilege felt like the right starting point, and so far it's held up.

Inter-agent comms and orchestration

This is the part that surprised me the most. Agents can trigger other agents on completion or failure:

yaml
yaml
on_complete:
  - agent: downstream-agent
on_failure:
  - agent: alert-agent

My restaurant checker is the best example. It finds available time slots, sends them to me on Telegram, waits for my reply, then triggers a separate booking agent with my selection as context. Two agents. One human decision in between. Each is stateless, has its own permissions, and does exactly one thing.

I made the chaining deliberately stateless. The downstream agent receives the upstream agent's output as context via the --with flag, but they don't share memory or conversation history. I found stateless agents much easier to debug and test. When something goes wrong, you can look at one agent in isolation instead of tracing shared state across a chain.

Interactive agents work through a fenced code block convention. The agent outputs an interaction block:

```interaction
{
  "message": "Found 3 slots at Bougainville",
  "options": [
    { "label": "19:00", "value": "Book 19:00" },
    { "label": "20:30", "value": "Book 20:30" }
  ]
}
```

The server parses that, routes it to Telegram, and waits for a reply. The reply becomes the prompt for the on_reply agent. Two stateless runs with a human decision in between.

Telegram integration uses grammY for long-polling. No webhooks, no public IP required. The bot connects outbound. You can also just text the bot directly, and a router powered by Claude Haiku picks the best-matching agent based on your message.

Agent observability

Once you have agents running in the background, you really want to know what they're doing. Agent Panel is the dashboard I built for that.

Every agent run shows up in real time: status, duration, token usage, cost, tools used, and the full activity timeline. Getting it connected is two environment variables:

bash
bash
# in ~/.agent-server/.env
AGENT_SERVER_PANEL_URL=https://app.agentpanel.dev
AGENT_SERVER_PANEL_API_KEY=ap_live_your_key_here

When the Agent Server sends its first event for a new agent, the Panel creates the record on the fly. No forms, no config screens. The agent definitions stay in your YAML files where they belong.

I also added heartbeat monitoring, which turned out to be more useful than I expected. Agents can hang. The Claude API can time out. Network connections can drop. If a run stops sending heartbeats for 90 seconds, the Panel marks it as failed. Without that, you'd get phantom "running" agents in your dashboard forever.

The run detail view has three tabs. Activity shows the full timeline of tool invocations and state transitions. Logs shows everything with level filtering. Output auto-detects links to Notion, Linear, GitHub, Slack, and other services, each with a branded icon. When my weekly status report agent creates a Notion page, the output tab shows a direct link to that page. That's my favorite small feature.

Agent Panel also conforms to the Google A2A protocol, so other agent frameworks can send telemetry to it. And you can use Claude Code hooks to send your regular Claude Code session telemetry to the same dashboard.

The Agent Panel dashboard showing live and completed agent runs

Run detail view showing the activity timeline of an agent in progress

What you can do with it

Here are the agents I actually run every day. These are the ones that have survived the "is this actually useful?" test:

Daily focus (5am, Tuesday through Saturday). Searches Slack, Linear, Notion for the last 24 hours of activity. Builds a ranked priority list. Creates a Notion page and sends me the link on Telegram. I read it over coffee. This one alone was worth the whole project.

Weekly status report (Wednesday 9am). Groups accomplishments by Linear Initiative, then Project, then Issue. Creates a structured Notion page with accomplishments, in-progress work, upcoming plans, and risks. I used to spend 45 minutes writing this every week. Now it's done before I wake up.

Weekly priority report (Monday 5am). Looks for leadership signals from our CEO, CTO, and COO in Slack, categorized by product/engineering impact, sales/GTM impact, and marketing impact. Combines those signals with my Linear work and upcoming deadlines to build a weighted priority list.

Linear artifact tracker (hourly). Polls Linear for new issues assigned to me. For each one, it determines the deliverable type (strategy doc, blog post, battle card, spec), searches for similar past issues, searches Notion for related documents, searches Slack for context, and drafts a first version into a Notion database. When I sit down to work on a new assignment, there's already a draft waiting with prior art pulled in.

Restaurant checker (on-demand via Telegram). I text the bot "Check Bougainville for 4 tonight" and it uses Playwright to check availability. Sends back time slots as Telegram buttons. I tap one.

One thing I've learned through all of this: agents that try to produce the final deliverable tend to generate mediocre output. But agents that prepare you to do the work? Those save hours every day. Every agent on this list prepares me for a decision. I still make the call.

Get it now

Both projects are open source on GitHub. I'd love feedback, PRs, or just to hear what you end up building with it.

Open source

You can also see Agent Panel in action at agentpanel.dev and read more on the Agent Panel app page.

Prashant Sridharan
Prashant Sridharan

Developer marketing expert with 30+ years of experience at Sun Microsystems, Microsoft, AWS, Meta, Twitter, and Supabase. Author of Picks and Shovels, the Amazon #1 bestseller on developer marketing.

Picks and Shovels: Marketing to Developers During the AI Gold Rush

Want the complete playbook?

Picks and Shovels is the definitive guide to developer marketing. Amazon #1 bestseller with practical strategies from 30 years of marketing to developers.