Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

.nornir/funnel-action-design.md

Funnel ACTION — design

The chosen design only. Alternatives/rationale belong in reasoning.

Doctrine

Ratified 2026-07-15 (Rickard, workspace_nordisk/PLAN.md #5): the funnel ACTION belongs to DWARVES, not nornir. nornir stays the QUERYABLE warehouse — you ask it about the system's layout, deps and history. dwarves is the ACTOR: it queries nornir and DOES constructive work. This is the L5a line — the warehouse (future / present / history) stays in nornir; every constructive operation is dwarves'.

Shape

 idea ──▶ dwarves::funnel_action
            │  gather_context ──(gRPC)──▶ nornir-server Warehouse.Tables   (READ leg)
            │        via nornir-warehouse-embed  (thin client, no nornir dep, no cycle)
            │
            │  ClaudeCaller.decompose(idea, ctx)
            │        via nornir-claude ClaudeTransport   (the ONE shared model client)
            │        prompt = instruction + system context + idea → STRICT JSON
            ▼
        FunnelPlan { idea, summary, nodes:[{id,kind,title,needs}] }   (a DAG)

PlanNode mirrors nornir's funnel node shape (kind / title / needs) so a produced plan maps 1:1 onto the funnel DAG (Funnel.AddNode + Funnel.Link) with no re-modelling.

Reuse, never twin (L5)

Need Consumed leaf Never
Talk to Claude nornir-claude (ClaudeTransport seam, OfflineTransport stub, HttpTransport) a bespoke reqwest client
Read system context nornir-warehouse-embed::fetch_warehouse_tables opening nornir's catalog.redb (2nd exclusive lock)
Submit an idea nornir-funnel-embed::submit_idea a twin funnel gRPC client
Find work (missing tests) autonom scan feeder + NeedsTestsReport a twin gap scan

ClaudeCaller wraps a boxed ClaudeTransport. ClaudeCaller::offline() is the deterministic stub; ClaudeCaller::live() (feature claude-http) is just nornir_claude::HttpTransport.

Login / credentials

Model auth is the nornir-claude credential chain, first hit wins: ANTHROPIC_API_KEY → ANTHROPIC_AUTH_TOKEN (OAuth bearer + anthropic-beta) → per-session token. Agentic use (Claude Code driving dwarves-server mcp) authenticates via the claude CLI OAuth token in ANTHROPIC_AUTH_TOKEN. nornir-server auth for the warehouse/funnel reads uses the nornir-funnel-embed token chain (NORNIR_SERVER_TOKEN → ~/.nornir/token) with NORNIR_SERVER / NORNIR_WORKSPACE.

Honest, never silent-empty (L4)

MCP surface — dwarves::mcp, served by dwarves-server mcp (feature mcp)

⚠ It was its own dwarves-mcp binary until 2026-08-03. MCP is a capability of the dwarves SERVICE, not a delivery beside it (Rickard: "mcp skall väl ingå inkompilerat i server!"), and <x>-server is the atom that means "everything compiled in", so the tools moved into the library and the second bin is gone. embed-all carries mcp. Whoever configured the old binary changes "command": "dwarves-mcp" to "command": "dwarves-server", "args": ["mcp"].

rmcp v1 (mirrors nornir's nornir-mcp): a ToolRouter of #[tool] methods behind a stdio ServerHandler, so Claude Code drives dwarves:

Blocking embed/scan calls run under tokio::task::spawn_blocking so the current-thread runtimes the thin clients build never nest-panic inside the MCP tokio runtime.

Feature gating (lean by default, L2)

funnel-action (module + embed clients) and mcp (rmcp/schemars + dwarves::mcp) are OFF by default; claude-http lights the live transport. The default/published forge links none of rmcp/reqwest/schemars.

Tests (L4, RED-when-broken; testmatrix rows)