Internal Ops Agent Template

An agent wired to Slack, email, and your database, running ops work on a schedule with tool approvals

What it is

A starting point for an internal agent that does operational work. It is connected to your tools (Slack, email, your database) and runs on a schedule. Sensitive actions go through a tool-approval gate so a human signs off before the agent sends or writes anything.

Recursiv ships hundreds of data integrations, so the same pattern extends to whatever your ops touch.

What it includes

PieceWhat it does
AgentA model-agnostic agent (any supported model) in permission tool mode
IntegrationsSlack, email, and more connected as agent tools
DatabaseA Postgres database for ops state and logs
ScheduleAn r.jobs cron that runs the agent’s routine
Tool approvalsA human approves or rejects each sensitive tool call before it runs

How to start it

Create the agent and its database, then connect integrations.

$npm install @recursiv/sdk
$export RECURSIV_API_KEY=sk_live_...
$export RECURSIV_ORG_ID=your-org-id
1import { Recursiv } from '@recursiv/sdk';
2
3const r = new Recursiv();
4const ORG_ID = process.env.RECURSIV_ORG_ID!;
5
6// 1. Create the agent in permission mode so tool calls require approval
7const { data: agent } = await r.agents.create({
8 name: 'Ops Agent',
9 username: 'ops_' + Date.now(),
10 model: 'anthropic/claude-sonnet-4.6',
11 system_prompt: 'You run internal ops. Summarize, draft, and flag. Never act without approval.',
12 tool_mode: 'permission',
13 organization_id: ORG_ID,
14});
15
16// 2. Give it a project and database for ops state
17const { data: project } = await r.projects.create({
18 name: 'ops-infra',
19 organization_id: ORG_ID,
20});
21await r.databases.ensure({ project_id: project.id, name: 'ops' });

Connect integrations

Connect the providers your ops touch. Browse the catalog with r.integrations.listApps() and r.integrations.getTools(provider).

1const { data: slack } = await r.integrations.connect({
2 provider: 'slack',
3 organization_id: ORG_ID,
4});
5
6// For reliable batch work you can run a tool yourself, no LLM in the loop
7const { data: emails } = await r.integrations.executeTool({
8 connection_id: slack.connection_id,
9 tool: 'GMAIL_FETCH_EMAILS',
10 arguments: { query: 'is:unread', max_results: 50 },
11});

Run on a schedule

A cron job runs the agent’s routine. Its handler_code executes in the project sandbox.

1await r.jobs.create({
2 name: 'daily-ops-digest',
3 cron: '0 8 * * *', // 8am daily
4 project_id: project.id,
5 handler_code: `
6 const { Recursiv } = require('@recursiv/sdk');
7 const r = new Recursiv();
8 await r.agents.chat('${agent.id}', {
9 message: 'Review overnight Slack and email. Draft a digest and flag anything urgent.',
10 });
11 `,
12});

Approve tool calls

Because the agent is in permission mode, sensitive tool calls wait for a human. Surface pending calls and approve or reject them.

1const { data: pending } = await r.integrations.listPendingExecutions(conversationId);
2
3for (const exec of pending) {
4 if (looksSafe(exec)) {
5 await r.integrations.approveExecution(exec.id);
6 } else {
7 await r.integrations.rejectExecution(exec.id);
8 }
9}

Key SDK calls

  • r.agents.create({ tool_mode: 'permission', model, system_prompt }) - the gated agent
  • r.databases.ensure({ project_id, name }) - ops state and logs
  • r.integrations.listApps(), r.integrations.getTools(provider), r.integrations.connect({ provider, organization_id }) - wire up tools
  • r.integrations.executeTool({ connection_id, tool, arguments }) - run a tool deterministically
  • r.jobs.create({ name, cron, handler_code, project_id }) - the scheduled routine
  • r.integrations.listPendingExecutions(conversationId), approveExecution(id), rejectExecution(id) - tool approvals

Next steps