A durable research agent.
Always-on web searches that notify you when new results show up. Like Exa Monitors or Parallel Monitor, except it runs as a serverless agent on your own OpenComputer account and uses whichever search API you already pay for.
- Vendor-neutral. Exa, Parallel, Tavily, Brave, Serper, Firecrawl, Jina, Perplexity, Linkup, You.com, Kagi, Valyu, Monid. One setting switches.
- One key. Only the secret for the provider named in
SEARCH_PROVIDERis required. The other twelve connections are registered but never fetched. - Your keys never enter the runtime. They are managed secrets attached at the outbound edge, never visible to the agent or its prompt.
- Delivery to a webhook (JSON, one POST per watch) and/or Slack.
- Every refresh is a durable session you can open in the dashboard.
or from a clone:
git clone https://github.com/diggerhq/openmonitor.git
cd openmonitor
npm install
npm run opencomputer -- login
printf %s exa | npm run opencomputer -- env set SEARCH_PROVIDER --value-stdin
printf %s "$EXA_API_KEY" | npm run opencomputer -- secrets set EXA_API_KEY --value-stdin
npm run deploy -- --watchThen run it once from another terminal:
npm run session -- "Refresh every watch with a 24 hour lookback."Edit opencomputer/agents/monitor/watches.ts. Each watch is a query and an
optional criteria sentence. With criteria, the agent reads every result
against it and drops the rest. Without, everything new is delivered.
{
id: "opencomputer-mentions",
query: "OpenComputer serverless agents",
criteria: "Mentions of OpenComputer (opencomputer.dev), the agent platform. Not 'open computer' hardware.",
}The schedule in schedules/refresh.ts runs daily at 09:00 UTC with a 24 hour
lookback. Change the cron and lookback_hours together.
printf %s parallel | npm run opencomputer -- env set SEARCH_PROVIDER --value-stdin
printf %s "$PARALLEL_API_KEY" | npm run opencomputer -- secrets set PARALLEL_API_KEY --value-stdin| Provider | SEARCH_PROVIDER |
Secret | Date filter |
|---|---|---|---|
| Exa | exa |
EXA_API_KEY |
startPublishedDate |
| Parallel | parallel |
PARALLEL_API_KEY |
source_policy.after_date |
| Tavily | tavily |
TAVILY_API_KEY |
start_date |
| Brave Search | brave |
BRAVE_API_KEY |
freshness range |
| Serper (Google) | serper |
SERPER_API_KEY |
tbs |
| Firecrawl | firecrawl |
FIRECRAWL_API_KEY |
tbs |
| Jina Reader | jina |
JINA_API_KEY |
client-side on publishedTime |
| Perplexity Search | perplexity |
PERPLEXITY_API_KEY |
search_after_date_filter |
| Linkup | linkup |
LINKUP_API_KEY |
fromDate |
| You.com | you |
YOU_API_KEY |
freshness range |
| Kagi | kagi |
KAGI_API_KEY |
client-side on published |
| Valyu | valyu |
VALYU_API_KEY |
start_date |
| Monid | monid |
MONID_API_KEY |
client-side; see providers/monid.ts |
Not included: SerpApi and Google Custom Search, because they take the key as a query parameter and managed secrets are only injected into headers.
- Every provider module declares its own connection with a
useSecretreference, so all thirteen connections are registered at deploy. Secrets are resolved at outbound dispatch, not at deploy, so unset secrets never block a deployment.opencomputer doctorwarns about them and nothing else. SEARCH_PROVIDERselects one provider at render time. Only that provider's connection is ever fetched; the other twelve are inert.- If the selected provider's secret is unset, the platform rejects the
outbound request at the edge with a "secret missing or not allowed for
this connection" error.
search_watchsurfaces it, the agent stops before trying another provider, and the run report names the exactsecrets setcommand. This is enforced by the platform, not by a check in this code. - The template manifest marks no secret as required. The deploy form shows
all thirteen, enforces none, and requires only
SEARCH_PROVIDER. - Switching is two commands: set the new secret, change the variable. The
old key can stay or be removed with
opencomputer secrets remove.
Webhook. Set the WEBHOOK_URL runtime variable. Each watch with new
results sends one POST:
{
"type": "monitor.results",
"watch": { "id": "opencomputer-mentions", "query": "..." },
"provider": "exa",
"sentAt": "2026-09-05T09:00:12.000Z",
"items": [
{ "id": "3f9c1a0b2d4e6f70", "url": "https://...", "title": "...", "why": "..." }
]
}items[].id is stable for a watch and URL across runs. Dedupe on it.
Slack. Scheduled results post to the monitor-alerts destination, and
you can @mention or DM the bot to run the watches now, ask "what's new about
X", or check the setup. One Slack thread is one durable session. Each
scheduled item is published once through the outbox; the id doubles as the
idempotency key, so a re-run never double-posts.
Connect it per environment with the helper, which drives the same API the dashboard uses:
scripts/slack.sh start development
# create the Slack app from slack-manifest.development.json at https://api.slack.com/apps
# (Create New App -> From a manifest), install it to the workspace, then:
SLACK_APP_ID=A… SLACK_SIGNING_SECRET=… SLACK_BOT_TOKEN=xoxb-… scripts/slack.sh complete development
# /invite @OpenMonitor in the alerts channel, copy its ID from channel details, then:
scripts/slack.sh bind development C0123456789
scripts/slack.sh status developmentDevelopment and Production have separate Slack connections; repeat for
production once the daily schedule should post there. The dashboard's
Channels page does the same thing with a wizard.
Copy opencomputer/agents/monitor/tools/providers/exa.ts, change the
connection and the field mapping, export a Provider, and add it to
providers/index.ts. The interface is one function:
search(query: string, { since: Date; limit: number }): Promise<Hit[]>agent.ts is a plain TypeScript function. On every step it selects the model
and three tools: list_watches, search_watch, deliver. The managed harness
runs the loop. The schedule starts a session; the agent walks every watch,
searches through the selected provider, applies the criteria, delivers, and
writes a one-line report per watch.
Dedupe across runs is by provider date filter plus the stable item id on the receiving side. There is no database in the project.
One click on OpenComputer. The reference runtime. Your account, your keys as managed secrets, every refresh a durable session you can open in the dashboard.
Fork it. A folder of TypeScript, no framework. Change a sentence in
watches.ts, redeploy.
Break glass. The runtime is replaceable. This is what it does for this project, so you can rebuild it if you ever want to:
- a scheduler that starts a session from the cron in
schedules/refresh.ts; - a loop that renders
agent.tsbefore every model step, calls the model, and executes the three tools; - durable session state, so a run survives a restart;
- secret injection at the outbound edge, so provider keys never enter the process;
- outbox delivery with retries and idempotency for Slack.
Durable Objects or Postgres for state, Lambda or a microVM for tool
execution, a cron for the schedule. Nothing in this repository assumes
more than the functions exported by @opencomputer/agent.