Skip to content

Instantly share code, notes, and snippets.

@mdp
Created May 18, 2026 19:32
Show Gist options
  • Select an option

  • Save mdp/0345152db729b44fc7a05f2480e3b500 to your computer and use it in GitHub Desktop.

Select an option

Save mdp/0345152db729b44fc7a05f2480e3b500 to your computer and use it in GitHub Desktop.
How BigBrotherBot uses @cline/sdk v0.0.41 — a comprehensive integration overview

BigBrotherBot × Cline SDK — Integration Overview

This document was produced by reviewing the BigBrotherBot codebase. It covers every meaningful way the project uses @cline/sdk.


Overview

BigBrotherBot is a production AI agent for 1984 Ventures. It processes inbound email, enriches a Notion CRM, scores applications, runs daily meeting prep, and provides a chatbot interface over Discord and WhatsApp.

Every LLM agent run — regardless of channel — flows through a single function: runClineSkill() in src/cline/harness.ts. There are no direct generateText calls anywhere in the agent path. @cline/sdk v0.0.41 is the sole framework.


Package

"@cline/sdk": "^0.0.41"

1. Core — ClineCore Lifecycle

src/cline/harness.ts

let _cline: ClineCore | null = null;

async function getCline(): Promise<ClineCore> {
  if (!_cline) {
    _cline = await ClineCore.create({
      clientName: "bigbrother",
      backendMode: "local",
    });
  }
  return _cline;
}

export async function disposeCline(reason = "bigbrother_shutdown"): Promise<void> {
  if (!_cline) return;
  const cline = _cline;
  _cline = null;
  await cline.dispose(reason);
}

One ClineCore instance held for daemon lifetime. disposeCline() on shutdown. abortClineSession(sessionId) wired to Discord canceller.


3. System Prompt Assembly

buildSystemPrompt(profile, agentContext, skillBody) layers:

[agentContext]        <- ASSOCIATE_PERSONA or CRM_PERSONA (.md files)
[rules block]         <- CHAT_RULES | EMAIL_RULES | CRON_RULES
<env>                 <- date [+ cwd for chat]
[skillBody]           <- SKILL.md Markdown content

Rules blocks:

CHAT_RULES: "Always gather context. Do not ask permission. Tool calls until done. Show planning."

EMAIL_RULES: "Complete without asking. Keep using tools until done."

CRON_RULES: "You work autonomously. Show planning. Call submit_and_exit when done. Verify work."


4. Run Profiles

Profile SDK mode enableTools reasoningEffort
chat act false medium
email act false --
cron yolo false --

cron also injects submit_and_exit tool and asserts it was called.


5. Autonomy Gate: submit_and_exit

const submitAndExitTool = createTool({
  name: "submit_and_exit",
  lifecycle: { completesRun: true },
  retryable: false,
  maxRetries: 0,
  inputSchema: {
    type: "object",
    properties: {
      summary: { type: "string" },
      verified: { type: "boolean" },
    },
    required: ["summary", "verified"],
  },
});

6. SDK Config

cline.start({
  config: {
    providerId: "openrouter",
    modelId,
    apiKey: env.OPENROUTER_API_KEY,
    reasoningEffort,
    cwd: process.cwd(),
    mode,
    enableTools: false,
    enableSpawnAgent: false,
    enableAgentTeams: false,
    disableMcpSettingsTools: true,
    extensions: [bigBrotherRuntimeExtension],
    extensionContext: {
      workspace: { rootPath: process.cwd(), cwd: process.cwd() },
    },
    systemPrompt,
    extraTools: toolsForProfile(profile, input.extraTools),
  },
  prompt: input.userMessage,
  initialMessages: input.initialMessages,
  interactive: false,
});

Key: enableTools: false, disableMcpSettingsTools: true, backendMode: "local".

2. The Single Entry Point — runClineSkill

Fields on ClineSkillInput:

Field Purpose
skillBody SKILL.md Markdown — agent instructions
userMessage The turn prompt
extraTools AgentTool[] — full tool belt
profile "chat" | "email" | "cron"
agentContext Persona text prepended to system prompt
initialMessages Prior thread messages
modelId Override model (default: OpenRouter anthropic/claude-sonnet-4-6)
mode / enableTools / reasoningEffort Per-run overrides
verbose / onToolEvent Tool-call streaming
abortSignal Cancel via AbortController
telemetry { functionId, userId?, properties? } — OTel span wrapping

Architecture at a Glance

Gmail poller / Discord / WhatsApp / Cron scheduler
        |
        v
  runClineSkill(input: ClineSkillInput)
        |
        +- buildSystemPrompt()     <- persona + rules + <env> + skill body
        +- (optional) subscribe() <- real-time tool event stream
        +- (optional) withAiSpan() <- OpenTelemetry + PostHog tracing
        +- cline.start({ config, prompt, initialMessages })
              ClineCore handles multi-step tool use internally

7. MCP Tool Integration

src/cline/mcp-manager.ts

BigBrother uses @ai-sdk/mcp (createMCPClient, HTTP transport) to connect to MCP servers.

getAllMcpTools() is the tool registry. On first call it:

  1. Connects to every MCP server lazily
  2. Calls client.listTools() for JSON Schema definitions
  3. Calls client.tools() to get AI SDK execute() wrappers
  4. Wraps each as a Cline AgentTool via createTool() with a prefixed name
  5. Caches the result for daemon lifetime

Tool name prefixes:

Prefix Server Examples
goog_* google-workspace goog_gmail_send, goog_calendar_list_events
notion_* notion + notion-generic notion_create_company, notion_update_contact
db84_* 84db (applications) db84_apply_list, db84_apply_update
research_* research MCP research_enrich_company, research_web_search
posthog_* posthog analytics tools

buildToolsForNames(toolNames, lightToolRegistry, threadId?) resolves a skill's declared tools:

  • Matches against the MCP tool cache
  • Falls back to the local light-tool registry
  • Wraps goog_gmail_send with inbound thread_id for email reply threading

8. Light Tools (Locally Defined)

Four tools defined in src/cline/tools/, not via MCP:

ask_crm -- Recursive Nested Run

Delegates Notion work to a nested runClineSkill() with CRM persona + Notion + research tools.

execute: async (input) => {
  const { task, context } = input;
  const crmTools = allMcp.filter((t) => CRM_TOOLS.has(t.name));
  return await runClineSkill({
    skillBody: CRM_PERSONA,
    userMessage: context ? `${task}\n\n--- Context ---\n${context}` : task,
    extraTools: crmTools,
    telemetry: { functionId: "ask-crm" },
  });
}

run_task -- Task Trigger

Lets chat agents trigger any enabled scheduled task with params. Runs synchronously.

loadSkill -- Dynamic Skill Loading

Pulls a skill body on demand from the registry.

run_research_subagent -- Model-Switched Subagent

Registered at startup. Uses GEMINI_FLASH (RESEARCH_MODEL) rather than default Claude.

9. Runtime Extension: bigbrother-runtime.ts

export const bigBrotherRuntimeExtension: AgentPlugin = {
  name: "bigbrother-runtime",
  manifest: { capabilities: ["rules", "hooks"] },
  setup(api) {
    api.registerRule({
      id: "bigbrother-tool-discipline",
      source: "bigbrother",
      content: "Use tool names exactly as provided. Custom BigBrother tools use snake_case names such as ask_crm, load_skill, run_task, and submit_and_exit.",
    });
  },
  hooks: {
    afterTool({ tool, result }) {
      if (isStructuredToolError(result.output)) {
        console.warn(`[cline/tool] ${tool.name} returned structured error: ${result.output.error}`);
      }
## 11. Observability

### Event Subscription

```typescript
unsubscribe = cline.subscribe((event) => {
  const sessionId = sessionIdFromEvent(event);
  if (sessionId) captureSessionId(sessionId);
  if (event.type !== "agent_event") return;
  const e = event.payload.event;
  if (e.type === "content_start" && e.contentType === "tool") {
    input.onToolEvent?.({ phase: "start", toolName: e.toolName, input: e.input });
  } else if (e.type === "content_end" && e.contentType === "tool") {
    input.onToolEvent?.({ phase: "end", toolName: e.toolName, output: e.output, error: e.error });
  }
});

Discord uses this for live status messages during tool calls.

OTel + PostHog Tracing

When telemetry is provided:

  • Creates an OTel span named cline.<functionId>
  • Calls cline.getAccumulatedUsage(sessionId) after the run
  • Attaches { inputTokens, outputTokens } to the span
  • NormalizingPostHogProcessor rewrites OpenRouter model aliases to canonical IDs

Error Events

Event Trigger
agent.llm_error exception inside withAiSpan
agent.step_limit finishReason === "max_iterations"
cron.job_failed task throws in scheduler
email.process_failed email thread handler throws
email.attachment_ocr_failed Mistral OCR fails

12. Three Entry Paths

Email (Gmail poller)

Gmail unread -> getThread -> enrichAttachments (OCR)
  -> MessageRouter.handle()
    -> resolveContext(msg) -> EntryProfile
      -> runClineEmailSkill()
        -> buildToolsForNames(skill.tools, ...)
        -> session history (<= 50 messages)
        -> runClineSkill({ profile: "email", agentContext: ASSOCIATE_PERSONA, ... })

Chat (Discord / WhatsApp)

@mention / WhatsApp message
  -> resolvePartner() -> gate on allowlist
  -> assemble: chat skill body + skill menu + all MCP tools + light tools
  -> session history (last ~8h, <= 10 messages)
  -> runClineSkill({ profile: "chat", ... })

Tasks (cron / CLI / runTask tool)

AgentScheduler (croner) / pnpm cli <task-id> / runTask tool
  -> task.run(params, agents)
## 14. Key Design Decisions

1. **Single ClineCore instance** -- lazy singleton held for daemon lifetime; `dispose()` on shutdown
2. **No built-in tools** -- `enableTools: false` on every run; all tools flow through `extraTools`
3. **Profile-based system prompt** -- `chat`/`email`/`cron` selects rules block, mode, and reasoning effort
4. **Recursive runs** -- `ask_crm` and `run_research_subagent` call `runClineSkill()` inside tool `execute()`
5. **Thread-ID injection** -- `goog_gmail_send` is wrapped with inbound thread ID so agents never supply it
6. **Structured error handling** -- all tool errors return `{ ok: false, ... }`; extension hook logs as warnings
7. **Autonomy gate** -- `cron` runs require `submit_and_exit` (`lifecycle: { completesRun: true }`) to complete
8. **Custom extension** -- `bigbrother-runtime` plugin adds a tool-naming rule and `afterTool` error hook
9. **MCP via `@ai-sdk/mcp`** -- not Cline's built-in MCP; BigBrother manages its own connections
10. **Telemetry** -- `cline.getAccumulatedUsage()` feeds PostHog/OTel token counts; events drive Discord live-status


    -> skill.tools -> buildToolsForNames(...)
    -> runClineSkill({ profile: "cron", skillBody, userMessage, agentContext: ASSOCIATE_PERSONA, ... })

13. Personas

Plain Markdown files:

  • src/agents/associate/AGENTS.md -- the Associate persona (email + task runs)

  • src/agents/crm/AGENTS.md -- the CRM specialist persona (used by ask_crm tool)

    }, }, };


The `afterTool` hook logs structured errors as warnings so the agent loop continues past recoverable failures.

---

## 10. Tool Error Structure

**`src/cline/tool-errors.ts`**

```typescript
interface StructuredToolError {
  ok: false;
  tool: string;
  error: string;
  errorName: string;
  retryable: boolean;
}

All tool execute() bodies catch and wrap errors in this structure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment