AWS Just Open-Sourced an Inbox for Your Background AI Agents — Here's What MERN Devs Need to Know

You know that feeling when you hand an AI agent a real task — scraping a dozen pages, refactoring a module, filing a bug report — and then you sit there watching a chat window like it's a loading bar? Yeah. AWS noticed that too.
Last week AWS open-sourced Pizza Bot, a self-hosted inbox for long-running background AI agents. It's a deceptively practical little project, and if you're building anything agent-adjacent in your Node.js apps, it's worth fifteen minutes of your time.
The Problem: Chatbots Are Terrible at Long Tasks
The way most of us run AI agents right now is basically a chat session with extra steps. You send a prompt, you watch it execute, you wait. That's fine for "write me a function" but it completely breaks down the moment you delegate something that takes three minutes — let alone thirty.
Pizza Bot flips the model. Instead of a synchronous chat, you submit a task and it runs in the background while you get on with your life. When it's done (or when it needs your sign-off), it surfaces in an inbox — think email, not Slack DMs. Three queues: All (history), Unread (completed work), Action (tasks waiting on your approval).
It's a small UX shift that changes how you think about agents entirely. You stop babysitting execution and start treating the agent like a junior dev working async.
What's Actually Under the Hood
The stack is worth a quick look because it maps neatly onto things MERN developers already touch:
LangGraph — handles stateful agent execution (this is Python territory, but the REST API layer bridges it)
DeepAgents — the agent framework on top of LangGraph
SQLite — lightweight persistence for task state and history
MCP (Model Context Protocol) — standard for wiring in tools (file system, browser, custom APIs)
Electron — desktop client wrapper, though there's also a browser and terminal client
What's clever here is the approval workflow. The agent can hit a pause checkpoint that persists durably across sessions — it literally waits until you approve the next step, even if your machine restarts. For anyone who's built multi-step workflows in Express with Redis queues, the mental model is familiar. It's just that the "worker" is now an LLM.
Model support is flexible: Anthropic, OpenAI, Gemini, Amazon Bedrock, or local Ollama. Which brings us to the cost angle.
Pairing Pizza Bot with Open-Weight Models (Smaug Just Launched)
Running every background agent task through a paid API gets expensive fast — especially for always-on bots or high-volume workflows. This week Abacus.AI released Smaug, a family of three open-weight models specifically tuned for agentic workloads:
| Model | Best For | Base |
|---|---|---|
| Smaug Agentic | Complex coding loops, long autonomous tasks | Kimi K3 (2T params) |
| Smaug Flash | Messaging bots (Slack, WhatsApp, Telegram) | DeepSeek V4 Flash |
| Smaug Mini | Multimodal tasks, enterprise chatbots | Qwen3.8 27B |
They report 15–20% gains on long-running agent loops vs. the base models. Weights are free on Hugging Face. Run Smaug Mini locally via Ollama and point Pizza Bot at http://localhost:11434 — your per-task cost drops to essentially electricity.
This is the combination that makes self-hosted agents actually viable: Pizza Bot for orchestration, Smaug for inference, Ollama for local serving. No vendor lock-in, no telemetry, data stays on your machine.
Wiring This Into Your Express Backend
Pizza Bot exposes a REST API you can call from any Node.js service. Here's a minimal integration that submits a background task and polls for completion:
// services/agentService.js
const PIZZA_BOT_URL = process.env.PIZZA_BOT_URL || 'http://localhost:3001';
/**
* Submit a long-running task to Pizza Bot and return a task ID.
* The agent runs async — your Express route returns immediately.
*/
export async function submitAgentTask(prompt, skill = 'default') {
const res = await fetch(`${PIZZA_BOT_URL}/api/tasks`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt, skill }),
});
if (!res.ok) throw new Error(`Pizza Bot rejected task: ${res.statusText}`);
const { taskId } = await res.json();
return taskId;
}
/**
* Check task status. Status can be: pending | running | awaiting_approval | done | failed
*/
export async function getTaskStatus(taskId) {
const res = await fetch(`${PIZZA_BOT_URL}/api/tasks/${taskId}`);
return res.json(); // { taskId, status, result, createdAt, updatedAt }
}
In your Express route, you fire the task and store the taskId in MongoDB — then expose a status endpoint your frontend polls (or use webhooks if you configure them):
// routes/reports.js
import { Router } from 'express';
import { submitAgentTask, getTaskStatus } from '../services/agentService.js';
import Task from '../models/Task.js';
const router = Router();
// POST /api/reports/generate
router.post('/generate', async (req, res) => {
try {
const { prompt } = req.body;
const taskId = await submitAgentTask(
`Generate a sales report: ${prompt}`,
'reporting' // a custom Pizza Bot skill
);
// Persist the task reference in MongoDB
await Task.create({ taskId, userId: req.user.id, status: 'pending' });
// Return immediately — don't make the client wait
res.status(202).json({ taskId, message: 'Report generation started' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// GET /api/reports/status/:taskId
router.get('/status/:taskId', async (req, res) => {
const status = await getTaskStatus(req.params.taskId);
res.json(status);
});
export default router;
On the React side, a quick polling hook handles the rest:
// hooks/useTaskStatus.js
import { useState, useEffect } from 'react';
export function useTaskStatus(taskId, intervalMs = 3000) {
const [status, setStatus] = useState(null);
useEffect(() => {
if (!taskId) return;
const poll = async () => {
const res = await fetch(`/api/reports/status/${taskId}`);
const data = await res.json();
setStatus(data);
if (data.status === 'done' || data.status === 'failed') {
clearInterval(timer);
}
};
const timer = setInterval(poll, intervalMs);
poll(); // immediate first check
return () => clearInterval(timer);
}, [taskId, intervalMs]);
return status;
}
Clean pattern. The agent does the heavy lifting async; your MERN app just tracks state and surfaces results.
Custom Skills — Where the Python Angle Comes In
Pizza Bot's "skills" system lets you package specialized agent behavior as SKILL.md files. Each skill defines what tools the sub-agent can access and how it should approach a category of tasks.
Here's where Python becomes your friend: if your skill needs to call a data science pipeline, run a pandas analysis, or invoke a trained ML model, you can expose that as an MCP tool backed by a FastAPI endpoint. The agent calls your Python service through MCP the same way it'd call the filesystem or a browser. You end up with a Node.js orchestration layer, a Python ML layer, and Pizza Bot gluing the async coordination together. It's a surprisingly natural split.
The Practical Takeaways
A few things worth taking away from all this:
Background agents are a different paradigm than chat agents. The inbox model forces you to think about what tasks are actually worth delegating vs. what you just want to watch. That's a good constraint.
Self-hosted is now a real option. Between Pizza Bot (Apache 2.0) and Smaug (free weights on Hugging Face), you can run a meaningful agentic workflow without a single API key. That's new, and it matters for privacy-sensitive projects.
MCP is becoming the connective tissue. If you're building anything that might eventually talk to an AI agent, wrapping your service as an MCP server now costs almost nothing and opens up a lot of future integration paths.
The approval workflow pattern is underrated. Durable pause-and-resume is genuinely hard to build well. Pizza Bot gives it to you for free. Steal the pattern even if you don't use the tool.
Wrapping Up
Pizza Bot isn't going to replace your existing Express APIs or your React components. What it does is give you a sane primitives for the "AI does work in the background while I do something else" pattern — which is honestly where most of the useful agent applications live.
Grab the repo at github.com/pizza-bot-app/pizza-bot, spin up Ollama with a Smaug Mini model locally, and wire it into one endpoint in your existing app. See how it feels. The barrier to entry is lower than it's ever been.
What tasks in your current MERN app are you still doing manually that an async agent could handle? Drop a comment — I'm curious what people are actually automating these days.





