Skip to content

Integrations Architecture

Overview

SecondBrain integrates three external services — Ollama, n8n, and Dify — without replacing any part of its cognitive core. The core (planning, policy, memory, retrieval, decisions, observability) remains inside SecondBrain. The external services are bounded adapters reachable only through governed interfaces.

┌──────────────────────────────────────────────────────────────────────┐
│                         SecondBrain (cognitive core)                 │
│                                                                      │
│  CLI ──► Agent ──► ToolExecutor ──► ToolRegistry ──► adapters        │
│                        │                                   │         │
│                   ToolPolicy                          EventLog        │
│                  (ALLOW/DENY/                        (audit trail)    │
│                  REQUIRE_APPROVAL)                                    │
└───────────────────────────────────────┬───────────────────────────────┘
                                        │  governed call
                    ┌───────────────────┼───────────────────┐
                    ▼                   ▼                   ▼
              ┌──────────┐       ┌──────────┐        ┌──────────┐
              │  Ollama  │       │   n8n    │        │   Dify   │
              │ (local)  │       │ (webhook)│        │  (HTTP)  │
              └──────────┘       └──────────┘        └──────────┘

What belongs inside SecondBrain (core)

  • Planning, multi-step agent loops
  • Policy evaluation and approval gating
  • Memory (working / episodic / durable)
  • Context pack assembly and retrieval
  • Decision catalog + event log
  • Provider chain (Anthropic, OpenAI, Gemini, Ollama)
  • Tool registry and executor
  • MCP orchestration
  • All governance and observability

What belongs outside (adapters)

  • Ollama: local model + embedding daemon — treated as a provider, not as the brain
  • n8n: external automation workflows — treated as governed tools, not as an orchestrator
  • Dify: external AI app surface — treated as specialized tools, not as the planner

Ollama

What already exists

brain/providers/ollama.py is a complete LLMProvider implementation supporting chat, tool calling, and streaming. It is registered in the provider chain as "ollama" and is the preferred local-first generation backend.

New: Ollama embeddings

brain/providers/ollama_embeddings.py adds OllamaEmbeddingBackend which calls Ollama's /api/embed endpoint. Activated when SB_OLLAMA_EMBED_MODEL is set.

brain/providers/embedding_factory.py exports build_embedding_backend() — a factory that returns OllamaEmbeddingBackend when SB_OLLAMA_EMBED_MODEL is set, or MemoryEmbeddingBackend (sentence-transformers) otherwise. The factory lives in brain.providers to avoid the pre-existing circular import in brain.memory.__init__.

Configuration

SB_OLLAMA_MODEL=qwen3.5:9b          # Chat model
SB_OLLAMA_EMBED_MODEL=nomic-embed-text  # Embedding model (enables Ollama embeddings)
SB_OLLAMA_HOST=http://localhost:11434
SB_OLLAMA_TIMEOUT=120
SB_OLLAMA_EMBED_TIMEOUT=60

Insertion points

Location What changes
brain/providers/__init__.py Already registered as "ollama" provider
brain/providers/embedding_factory.py build_embedding_backend() factory — call this instead of constructing MemoryEmbeddingBackend directly
brain/state/vector_store.py Migrate constructor to use build_embedding_backend() when needed

n8n

Design rationale

n8n is an automation plane, not a cognitive layer. Its workflows are side-effectful jobs (send emails, update CRMs, trigger CI, etc.) that need the same policy and approval treatment as any other destructive tool.

The architecture maps n8n workflows → ToolSpec entries registered in the existing kernel ToolRegistry. Every invocation passes through ToolExecutor:

agent calls tool "n8n.send_summary"
  → ToolExecutor.execute()
      → ToolPolicy.evaluate()   (ALLOW / DENY / REQUIRE_APPROVAL)
      → budget.claim_tool_call()
      → N8nClient.invoke_workflow()  (HTTP POST to n8n webhook)
      → EventLog.log_event("n8n.workflow.invoked")
      → ToolResult returned to agent

No new approval system, no new tracing — it's free via the existing kernel.

MCP migration path

n8n ships a native MCP server node (n8n-nodes-mcp). When that is configured, register n8n as a RegisteredMCPServer in brain/mcp/registry.py with transport="streamable_http" and the MCP endpoint URL. The existing MCPExecutor + MCPApprovalManager then handle everything. The HTTP adapter in brain/integrations/n8n/client.py is the fallback when MCP is not available.

Configuration

SB_N8N_ENABLED=true
SB_N8N_BASE_URL=http://localhost:5678
SB_N8N_API_KEY=your-api-key
SB_N8N_TIMEOUT_SECONDS=30
SB_N8N_MAX_RETRIES=2

Side-effect classification

SideEffectLevel Maps to SafetyClass Policy outcome
READ_ONLY READ_ONLY Always allowed
EXTERNAL_FETCH NETWORK Allowed in normal mode
SIDE_EFFECTFUL DESTRUCTIVE REQUIRE_APPROVAL
DESTRUCTIVE DESTRUCTIVE REQUIRE_APPROVAL

Setting requires_approval=true on a workflow also forces SafetyClass.DESTRUCTIVE, even for otherwise network-only workflows.

Exposing SecondBrain tools to n8n (inbound direction)

n8n can call back into SecondBrain via: 1. The n8n HTTP Request node pointed at sb serve (if an HTTP gateway is enabled) 2. The existing MCP server surface (brain/mcp/) if n8n's MCP client node is used

Exposure is explicit allowlist only — no tool is auto-exposed. Use RegisteredMCPServer.allowed_tools or equivalent HTTP gateway allowlist.


Dify

Design rationale

SecondBrain already provides everything Dify offers natively: RAG, agent loops, workflow runner, multi-provider LLM, context packs, document ingest. Dify is therefore not required and should only be enabled when:

  1. Another team already runs Dify and has specialized flows SecondBrain should consume
  2. Document extraction flows are prototyped in Dify before being ported native
  3. A non-technical user builds Dify apps and wants SecondBrain to call them

Dify apps are registered as ToolSpec entries in the same ToolRegistry and pass through the same ToolExecutor / EventLog pipeline as n8n workflows.

Configuration

SB_DIFY_ENABLED=true
SB_DIFY_BASE_URL=http://localhost/v1
SB_DIFY_API_KEY=app-your-api-key
# Named apps:
# SB_DIFY_APP_DOC_EXTRACTOR_API_KEY=app-your-doc-extractor-key
# SB_DIFY_APP_DOC_EXTRACTOR_BASE_URL=http://localhost/v1
SB_DIFY_TIMEOUT=60
SB_DIFY_MAX_RETRIES=2

app_id="default" uses the global SB_DIFY_* settings. Any other app_id must have an app-scoped API key in SB_DIFY_APP_<APP_ID>_API_KEY, with an optional app-scoped base URL override.

Response normalization

All Dify response shapes are normalized to DifyResult in client.py before leaving the integration module. No Dify-specific field names appear in agent code or memory entries.


Trust Boundaries

Service Trust tier Network Notes
Ollama (local) internal localhost only No auth needed; restrict to 127.0.0.1
n8n (local Docker) trusted_internal Docker network API key required
Dify (local Docker) trusted_internal Docker network App API key required
n8n (cloud/remote) external HTTPS Treat as external; require approval for all writes
Dify (cloud/remote) external HTTPS Treat as external; require approval for side effects

Deployment

The deploy/docker-compose.yml file starts all three services in a shared Docker network (sbstack). SecondBrain itself runs on the host and connects via localhost:PORT.

Host machine
├── sb (CLI, Python)  ─── connects to ───► sbstack network
└── Docker network sbstack
    ├── sb_ollama     :11434
    ├── sb_n8n        :5678
    ├── sb_dify_nginx :80
    ├── sb_dify_api
    ├── sb_dify_worker
    ├── sb_dify_web
    ├── sb_dify_db
    └── sb_dify_redis

Linux networking note: Dify containers cannot reach localhost:11434 on the host. Set DIFY_OLLAMA_BASE_URL=http://172.17.0.1:11434 (Docker bridge IP) or bind Ollama to 0.0.0.0 and use the host IP.

Mac/Windows: use host.docker.internal:11434 — already the default.

Startup sequence

# 1. Start the stack
docker compose -f deploy/docker-compose.yml --env-file .env.stack up -d

# 2. Pull Ollama models (one-time)
docker compose -f deploy/docker-compose.yml --env-file .env.stack \
  --profile pull-models run --rm ollama_pull

# 3. Verify all services
python scripts/check_stack.py

# 4. Set SecondBrain env vars (in .env file)
#    SB_OLLAMA_HOST=http://localhost:11434 ...
#    SB_N8N_ENABLED=true ...
#    SB_DIFY_ENABLED=true ...

# 5. Test CLI
sb n8n status
sb dify status
sb ollama status
sb stack check

Feature flags and default behavior

All three integrations are off by default. With no env vars set, the repo behaves exactly as before this integration was added.

Flag Default Effect when true
SB_OLLAMA_EMBED_MODEL (unset) Use Ollama /api/embed for vectors
SB_N8N_ENABLED false n8n starter tools appear in ToolRegistry; sb n8n commands work
SB_DIFY_ENABLED false Dify starter tools appear in ToolRegistry; sb dify commands work

Extension points

  1. Starter n8n workflows: webhook_ping, send_summary, and trigger_ingestion are now registered by default when n8n is enabled.
  2. Starter Dify apps: doc_extract and qa_assistant are now registered by default when Dify is enabled.
  3. Embedding selection: VectorStore and memory semantic paths now use build_embedding_backend() consistently.
  4. n8n MCP preference: n8n now prefers MCP when SB_N8N_MCP_SERVER_ID is configured and healthy, then falls back to HTTP.
  5. Inbound from n8n: inbound exposure is default-deny and controlled by SB_N8N_INBOUND_ALLOWED_TOOLS or MCP allowed_tools. On sb serve, the dedicated HTTP surface is POST /integrations/n8n/tools/{tool_id}/invoke.