← All use casesUSE CASE

Standup Collector that Chases So You Don't

Pings every teammate for their update, compiles one tidy standup, posts it — and books its own tomorrow.

schedulememoryagentscustom-tools

model: claude-sonnet · budget: $2/day · agent.js: ~97 lines

▶ Demo video — coming soon
75 seconds, deploy to running agent
VIEW SOURCE ON GITHUB ↗agent.js ↗

DEPLOY IT

git clone https://github.com/anupsinghinfra/oncell-cookbook && cd oncell-cookbook
cp .env.example .env   # add your ONCELL_API_KEY
npm run deploy standup-collector

# invoke it
curl -X POST https://api.oncell.ai/api/v1/agents/standup-collector/submit \
  -H "Authorization: Bearer $ONCELL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"smoke","update":"Done: deployed standup-collector. Doing: nothing. Blocked: nothing."}'

The async standup has a universal failure mode, and it isn't the format. Someone posts the thread at 9am. Three people reply. Two reply at 4pm in a different channel. One never replies, and nobody notices until Thursday, when it turns out they were blocked all week. The "bot" that was supposed to fix this is usually a webhook that posts a template and then does absolutely nothing else — because the hard part of standups is not the posting. It's the chasing, the remembering who replied, and the doing it again tomorrow.

Chasing is a stateful, time-driven job. You need somewhere durable to keep the roster and today's replies. You need a timer that fires "compile the summary in two hours" and another that fires "start again tomorrow" — timers that survive deploys and dead hosts. That's a database, a scheduler, and a worker before you've written a single sentence of standup logic.

standup-collector is one file, because all three of those are runtime primitives.

Two wake intents per day, zero cron

The identity instructions in agent.js end the collect procedure like this:

"Then call schedule twice: once with in set to 2 hours and note set to compile, once with in set to 1 day and note set to collect."

Each schedule call records a durable wake intent in the runtime's park ledger. The run then exits normally — no process waits around. Two hours later the runtime starts a fresh run with compile as its prompt; tomorrow it starts another with collect. Redeploy the agent in between, lose the host, it doesn't matter: the intents belong to the runtime, not the process. The identity tells the agent how to interpret those wakes — "A note reading collect means run the collect task" — so the chain is self-describing and self-sustaining.

The state layer is two memory shapes, declared right in the identity: a roster key holding { name, handle } entries, and one update:<YYYY-MM-DD>:<name> key per submitted update. Durable KV, no schema, no migration, survives everything.

An agent that talks to another agent

The compile procedure has the line that makes this a cookbook recipe rather than a toy:

"...then invoke the daily-digest agent task add-note with text set to a one-line summary of the standup; if that invoke fails, skip it without complaint."

That's the agents capability — declared in one line:

capabilities: [tools.memory, tools.schedule, tools.agents, sendMessage],

If you've deployed daily-digest from this cookbook, your standup now automatically feeds your morning briefing. Two agents you deployed separately compose into a pipeline, with no message bus, no shared database, and graceful degradation written as one clause of English.

send_message is the only stub in the file — a custom tool whose run body you point at Slack or Teams. It's declared exactly like a prebuilt tool (params is the JSON schema the model sees) and lives in source only; the manifest never carries it.

Cheap where it can be

The pings and the submissions don't need deep thinking, so their loops are tight:

const result = await agent.llm("Task: submit " + JSON.stringify({ name, update }), {
  maxSteps: 6,
  maxCost: 0.15,
});

The compile pass gets more room (maxSteps: 16, maxCost: 0.5), and the identity budget caps the whole agent at perDayCents: 200 — two dollars a day, enforced by the runtime's metering, not by hope. There's also a belt-and-braces cron registration (agent.schedule("daily-collect", "daily", ...)) so the habit starts even before the first self-scheduled wake exists.

Deploy it

git clone https://github.com/oncell/oncell-cookbook && cd oncell-cookbook
cp .env.example .env   # add your ONCELL_API_KEY
npm run deploy standup-collector

Add your team, then kick off the first cycle:

curl -X POST https://api.oncell.ai/api/v1/agents/standup-collector/add-member \
  -H "Authorization: Bearer $ONCELL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "dana", "handle": "@dana"}'

curl -X POST https://api.oncell.ai/api/v1/agents/standup-collector/collect -d '{}'

Teammates reply via submit (wire your messenger's slash command at it), and two hours later the compiled standup — Done, Doing, Blocked, plus who ghosted — lands in the channel.

What you didn't have to build

A cron service and the box it runs on. A store for the roster and today's replies. Retry logic for the compile job. A "who hasn't answered" tracker. An integration bus between your standup tool and your digest tool. The runtime supplied durable time, durable memory, agent-to-agent calls, and a hard daily spend cap.

You wrote a roster shape and two procedures in English. One command deploys the whole ritual.