Picks and Shovels: tech marketing for the AI era.

Get your copy
|11m read

I built agents that work while I sleep

A lightweight agent system that runs on my Mac, reuses every MCP server and skill I've built up in Claude Code, and gives me guardrails I actually trust. Here's what it does and how it's built.

I built agents that work while I sleep

Every morning around 7am I open Notion and there's a page waiting for me. A ranked list of what to work on that day, pulled from Slack, Linear, and Notion, prioritized by deadline and signal. I didn't do any of that work. An agent did, at 5am, while I was asleep.

I also wake up to competitive intel from the past week, a weekly status report written in my voice, drafts of Linear issues with prior art already linked, and a Saturday-morning career coaching note that compares my week's work to where a senior marketing leader should be pushing. None of it is perfect. All of it is a head start.

I built this over a few weekends. It saves me roughly ten hours a week. Here's what's in it and why I didn't use something that already exists.

Why not use OpenClaw?

OpenClaw is excellent. I tried it. I loved it. If you're looking for a polished, full-featured agent platform with an active community, it's a great choice.

I wanted something else. Something smaller. Something that ran on my Mac with no cloud dependency, reused every MCP server and skill I'd spent months tuning in Claude Code, and had guardrails I could read in one file. I wanted to be able to trust it to run while I was asleep without wondering what it might do.

So I built it myself. About a weekend of work for the first version, and I've been sharpening it ever since.

The payoff is that my agents inherit everything I've already invested in Claude Code. Every MCP server I've configured (Linear, Notion, Slack, Parallel, Hex, Gmail, Canva, Figma). Every skill I've written. Every hook. Every approved tool. The agents don't re-auth, don't duplicate integrations, don't need a separate tool config. The agent system is just Claude Code on a schedule, with a few safety rails.

The two pieces

There are two components. They work together but each stands on its own.

Agent Server is a Node.js daemon that runs on my Mac. It reads agent definitions from a folder, evaluates cron schedules every 60 seconds, and runs matching agents through the Claude Agent SDK. It keeps local state on disk. It works offline. If Agent Panel is down, Agent Server keeps working.

Agent Panel is a Next.js web app and iOS app for observability. Every run reports its telemetry to the Panel: status, duration, tokens, cost, tools used, full activity timeline, generated artifacts. I can see everything live from my phone. It also persists history across machines so a clean reinstall doesn't lose my run history.

The Agent Server is local-first. The Agent Panel is optional. If you only want to run agents locally and watch them in a menu bar app, you can. The Panel adds richer observability and cross-device history. The two stay in sync: Agent Server reports every run to the Panel, and on startup seeds its local store from the Panel's history. Duplicates are deduped by run ID, with local values preferred for anything the daemon owns.

Here's how the pieces fit together:

Agent Server also ships as a native macOS menu bar app. Click the title to open the dashboard. Click the gear to edit agent files directly. Right-click the server status pill to restart the daemon. It never appears in the Dock or Cmd-Tab. It's just there when you need it.

The macOS menu bar app showing agent definitions

The macOS app showing a live agent run with activity timeline

How agent definitions work

You define an agent as a Markdown file with YAML frontmatter. Here's the simplest possible one:

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

For longer prompts I use the full Markdown body. My weekly status report prompt is 170 lines of instructions, headers, numbered steps, and examples. YAML would have been miserable for that. Markdown with frontmatter is the sweet spot.

Each agent file lives in ~/.agent-server/agents/. The server watches the folder. Add a file, it gets picked up within seconds. Edit a file, same thing. I can also trigger any agent manually from the command line while I'm iterating on a new prompt.

The macOS app has a built-in editor with two tabs: PROMPT (the body) and CONFIGURATION (the YAML). Both are editable, both save atomically, and there's an Enabled switch next to the filename so I can flip an agent off without deleting it.

Running agents on a schedule

The server runs a timer every 60 seconds. Each tick it evaluates every agent's cron expression against the current time. If one matches, it tries to acquire a PID-based file lock, calls query() from the Agent SDK with the prompt and allowed tools, streams the events back for logging, and releases the lock in a finally block.

One thing I ran into early: my laptop sleeps. Agents miss their windows. So I added a sleep/wake catch-up flag. My daily focus agent is scheduled for 5am. If my laptop is asleep at 5am and I open it at 7am, the agent fires immediately rather than waiting another 24 hours. Set AGENT_SERVER_CATCH_UP=true in the env file and missed runs catch up on wake.

Agents can also watch file system paths instead of running on a clock:

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

I use this sparingly. Most of my agents are scheduled. The ones that react to file changes are mostly experiments.

Safety, because agents that run unattended need it

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 let agents run unattended, you need guardrails you trust.

Permissions are glob-based allow and deny lists. Deny always wins:

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 the SDK calls before every tool invocation. If a tool isn't in the allow list, or it's in the deny list, the call is blocked. The agent sees a rejection and the loop continues.

I think of agents in two phases. The first phase is agents that go out, gather information, and put it somewhere I can read it. A Notion page. A Telegram message. A draft in a database. Most of my agents stop here. The second phase is agents that act on the information: post as me, book a reservation, respond to a message. Those are riskier and I'm not building them yet. Not until I've lived with the first phase long enough to trust it.

The permission model is only half the safety story. The other half is reliability, which took me longer to build and was harder to get right. And remember, I'm not a real developer, so I'm still struggling with this. Runs can hang. The Claude API can time out. The network can drop. Laptops sleep. Making sure the agents are resilient in the face of these issues is a lot of work. My goal is to build self-healing agents.

Inter-agent orchestration

Agents can trigger other agents on completion or failure:

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

The chaining is deliberately stateless. The downstream agent receives the upstream output as context via the --with flag. They don't share memory or conversation history. If something breaks, I look at one agent's log, not a shared-memory trace across three agents.

Interactive workflows work through a fenced code block convention. An agent outputs an interaction block with a message and a list of options:

```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 it, routes the message to Telegram, and waits for a reply. My reply becomes the prompt for the on_reply agent. Two stateless runs with one human decision in between.

Telegram integration uses grammY with long polling. No webhooks, no public IP required. I can also text the bot directly and a Haiku-powered router picks the best-matching agent based on what I said.

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. Getting an agent connected is two environment variables:

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

Every run reports in real time. The dashboard updates through Supabase Realtime subscriptions, so I can watch a run's turn count tick up live, or see a new artifact appear the moment my agent creates it. There's no refresh button. There doesn't need to be one.

The home page shows a card per agent with total runs, success rate, average duration, total cost, last run, and next run. A Tasks Planned Today card lists every agent that's going to fire before midnight. An Artifacts card merges output from the Agent Server's local store with URLs extracted from run summaries: Notion pages, Linear issues, Google Docs, each with the right branded icon.

The run detail view has tabs for Activity, Logs, and Information. Activity is the live timeline of tool invocations and state changes. Logs is everything with level filtering. Information has the run summary, identifiers, token usage, and action buttons (Cancel stuck run, Delete). If a run has been stuck in "running" for longer than four minutes, a Cancel button appears in the Information box so I can reclaim it manually if the heartbeat sweep hasn't yet.

Agent Panel speaks the Google A2A protocol, so other agent frameworks can send telemetry to it. You can also use Claude Code hooks to ship your regular Claude Code session events to the same dashboard. Useful if you want one place to see both your interactive and background work.

The Agent Panel dashboard showing live and completed agent runs

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

The agents I actually run

These are the ones that survived the "is this actually useful?" test. The rest I deleted or turned off.

Daily focus (5am, Tuesday through Saturday). Searches Slack, Linear, Notion for the last 24 hours of activity. Builds a ranked priority list with deadlines and signal strength. 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). Scans company Slack for leadership signals, 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.

Career coach (Saturday 7am). Reads my week's work across Linear, Notion, Slack, and my calendar, then writes me a one-page reflection: what I shipped, what I punted, where my time actually went, and two or three areas a senior marketing leader in my position should be sharpening. More of a weekly mirror than a to-do list. I've learned more about my own patterns in six weeks of reading these than in six years of self-review attempts.

Linear artifact tracker (hourly). Polls Linear for new issues assigned to me. For each one it figures out the deliverable type (strategy doc, blog post, battle card, spec), searches for similar past issues, pulls related documents from Notion, 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 linked.

Competitive intel tracker (weekly). Visits competitor websites, checks blogs, pricing pages, and changelogs for changes. Searches the web for recent mentions, customer wins, new partnerships, and analyst coverage. Compiles it all into a Notion page organized by competitor. I used to do this manually once a quarter and it took a full day. Now it runs every Monday and I skim the output in five minutes. Most weeks nothing interesting happens. But the weeks something does, I know about it before anyone else on the team.

I also have a restaurant checker on Telegram that uses Playwright to find available reservations, but it's slow and half-working and I'm still experimenting with it. Conversational agents are hard.

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

Where the craft is going

Marketing has always been about knowing your product, your customers, and your market. That's still true. It's just not enough anymore.

Agents let you offload the repetitive, time-consuming parts: research, drafts, status reports, competitive monitoring. The stuff that eats your calendar but doesn't require your judgment. The result is more time for strategy, positioning, and the creative work that still needs a human in the room.

The agents I built save me about ten hours a week. Two hours of weekly status writing. Three hours of competitive monitoring. Four hours of daily triage across Slack, Linear, and Notion. One hour of artifact prep for new assignments. Ten hours I now spend on strategy, on writing this blog, on the parts of the job that still need me to be in the room.

You don't have to build your own. If you want the benefits without writing TypeScript on a Sunday, there are good options: OpenClaw for a full platform with community momentum, Dust for agents connected to your company's tools, Hyper for agentic workflows aimed at marketing teams.

But if you've invested deeply in Claude Code already (built up your MCP servers, written skills, tuned your prompts), a small local daemon that inherits all of it is the fastest path I've found.

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.