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:
- Another team already runs Dify and has specialized flows SecondBrain should consume
- Document extraction flows are prototyped in Dify before being ported native
- 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¶
- Starter n8n workflows:
webhook_ping,send_summary, andtrigger_ingestionare now registered by default when n8n is enabled. - Starter Dify apps:
doc_extractandqa_assistantare now registered by default when Dify is enabled. - Embedding selection:
VectorStoreand memory semantic paths now usebuild_embedding_backend()consistently. - n8n MCP preference: n8n now prefers MCP when
SB_N8N_MCP_SERVER_IDis configured and healthy, then falls back to HTTP. - Inbound from n8n: inbound exposure is default-deny and controlled by
SB_N8N_INBOUND_ALLOWED_TOOLSor MCPallowed_tools. Onsb serve, the dedicated HTTP surface isPOST /integrations/n8n/tools/{tool_id}/invoke.