OOO-Pilot — Story
Inspiration
Every out-of-office message makes the same promise: "I'll respond when I'm back." It's a lie of omission. The auto-responder ends the conversation instead of continuing it, so the actual information a coworker was carrying — a client deadline, a correction to a ticket's status, a blocker nobody logged — never reaches the absent person. They come back to a pile of unread messages, not a handoff.
The insight that started this project: that missing conversation is specifically a phone problem. A Slack bot can answer one question and lose the thread. A form can't ask a follow-up or push back on an ambiguous answer. A phone call can. So the question became — what if an OOO auto-responder could actually have that conversation on your behalf, the way a competent EA would if you had one?
CALL-E — "Your Code Is Calling" — made that buildable in a weekend instead of a quarter: an SDK that turns a task description and a phone number into a real, structured phone conversation.
What it does
OOO-Pilot is a temporary AI work proxy. While an employee is out of office:
- A coworker asks Slack (or calls a number, texts it, or hits a web widget) for a status update.
- OOO-Pilot assembles a verified briefing from Jira and recent local git commits.
- CALL-E places a real outbound phone call and has the actual conversation — explaining status, answering follow-ups from the verified context only, and asking what the absent employee should know.
- Everything is logged with a source and a trust level: Jira and git are
verified = 1; anything a coworker says on the call isverified = 0until a human confirms it. - When the employee returns, they get one summary — including a discrepancy report when something said on a call contradicts what Jira still shows.
That last part is the feature that makes the whole thing worth building: knowledge that only existed in someone's head, surfaced and reconciled against the system of record, because a phone call happened and was recorded structurally instead of just "handled."
How we built it
One Node.js process, one SQLite file. No vector database, no RAG, no microservices — the scope didn't need it and the honesty of a small, inspectable system mattered more than architecture-resume points.
- Slack (Socket Mode) is the entire frontend —
/ooo on|off|status|syncand@mention— there's no separate dashboard for the demo path. - Jira is the authoritative source.
POST /sync/jirapulls issues and every comment as separate, individually-ranked knowledge items, so a fresh comment can outrank a stale issue body. - Local git (
git logon a working tree, no push, no credentials) turns commits made while the employee was away into a "RECENT CODE CHANGES" section of the call brief — code context CALL-E didn't have any native path to before. - CALL-E's TypeScript SDK places the call with a
resultSchemaso the platform extracts questions, answers, new information, follow-ups, decisions, and commitments directly — with an LLM-based transcript re-extraction as a second pass if CALL-E can't materialize a schema-valid result. - Inbound is a deliberate workaround, stated honestly. CALL-E is outbound-only — there's no API to answer a ringing phone — so every inbound channel (missed call, SMS, a web widget, an MCP tool call) normalizes into one
InboundRequestrecord and triggers an immediate outbound callback instead. Not a limitation we hid; a limitation we designed around and documented. - MCP server exposes the same knowledge store as semantic tools (
search_employee_context,record_new_information,request_callback, …) so any agent host, not just Slack, can read an absent employee's context or trigger a call.
The verified flag is the load-bearing idea in the whole data model. Formally, for a knowledge item $k$:
$$ \text{verified}(k) = \begin{cases} 1 & k.\text{source} \in {\text{JIRA},\ \text{GIT}} \ 0 & k.\text{source} \in {\text{CONVERSATION},\ \text{MANUAL}} \end{cases} $$
Everything downstream — the discrepancy check, the return summary, the safety policy — is just careful bookkeeping around that one distinction.
Challenges we ran into
Building the happy path — Slack → context → CALL-E → SQLite → summary — took about a day and matched the intended build order closely. Almost all the remaining time went into making a real phone call actually, reliably happen:
- Slack scopes ≠ Slack event subscriptions.
@mentionsilently did nothing for a long stretch.app_mentions:readwas granted and the app reinstalled, but theapp_mentionbot event itself had never been added under Event Subscriptions — a separate, easy-to-miss step from granting the scope. Found it by adding raw Bolt middleware that logs the type of every Socket Mode payload, which turned "nothing is arriving" into "something arrived and crashed." - A context-pollution bug of our own making. A failed call's telephony error text was being written into the conversation's
summaryfield — which the next call's context builder reads as if a coworker had said it. Fixed by never writing a telephony failure intosummaryand only feeding completed conversations into future context. - The concurrency limit cost real credits. CALL-E's shared line allows exactly one concurrent call per account:
$$ \text{active_calls} \le 1 $$
A leftover local server process from earlier testing was still holding a call open, so a second call was rejected — and still billed. Fixed by refusing a second call locally rather than sending it to CALL-E, with a 10-minute staleness window so a killed process can't wedge the app shut permanently.
- International call termination. Repeated calls to a
+91number failed with SIP408/404— accepted, dialed, zero duration, zero transcript — on both the shared line and a purchased dedicated US number, despite one earlier call to the same number succeeding. Bisected with minimaltest:call/inspect:callscripts; ultimately worked around by testing with a US number and logged as direct feedback to the CALL-E team rather than treated as solved. - Diagnosis over guessing. Most of the debugging scripts in the repo (
check-scopes,check-channels,dump-payload,inspect-call) exist because "did you reinstall the app?" and "is the number reachable?" aren't questions documentation can answer — only the running system can. Kept them in because the next person integrating CALL-E will hit some of the same walls.
What we learned
- The hardest part of "AI makes a phone call" was never the AI. It was the boring infrastructure around it — event subscriptions, concurrency limits, telephony error codes — the same category of problem every phone integration has had since call centers existed.
- A verified/unverified distinction is worth more than a smarter model. The discrepancy report isn't a clever prompt; it's a database column enforced honestly. Treating a coworker's claim as evidence to reconcile rather than fact to insert is what makes the system trustworthy enough to actually hand someone a return-to-work summary.
- State the boundary instead of hiding it. OOO-Pilot doesn't answer live inbound calls, because CALL-E doesn't expose that capability yet — so the README says exactly that, and the code names the seam (
handleInboundRequest()) where a native inbound endpoint would slot in later. That honesty made the safety review and this write-up much easier to trust. - Keep the demo runnable without live infrastructure.
CALLE_DRY_RUN=trueand seeded Jira data mean the whole pipeline — context, extraction, discrepancy detection, return summary — can be rehearsed without spending call credits or requiring real credentials, which made iteration dramatically faster.
What's next
- A native CALL-E inbound endpoint, added as one more channel behind the existing
handleInboundRequest()seam. - Voice identity verification before disclosing project context on a call.
- Confluence and a hosted GitHub/GitLab API as additional
KnowledgeProvidersources — the interface is already the seam for this. - Suggested Jira transitions from confirmed discrepancies, proposed to the returning employee but never applied automatically.
- Multi-employee routing, so a team can share one OOO line instead of one employee per line.
Links
- Repo: https://github.com/balajianbalagan/ooopilot
- Landing page: https://balajianbalagan.pages.dev/
- Awesome Phone Call Agents listing (PR): https://github.com/CALLE-AI/awesome-phone-call-agents/pull/541
- Contact: vijibalaji2003@gmail.com
🤖 Generated with Claude Code
Built With
- bolt
- call-e
- claude
- openai
- slack
Log in or sign up for Devpost to join the conversation.