# LoomCycle architecture diagram
# Render: d2 docs/architecture.d2 docs/assets/architecture.png

vars: {
  d2-config: {
    layout-engine: elk
    pad: 32
  }
}

direction: down

# ---- External callers (left) ----

clients: "App servers · CLIs · TS / Python SDKs\nClaude Code & other MCP orchestrators (v0.8.15+)\nLangChain / LlamaIndex / n8n via OpenAI-compat shim (v0.11.3+)" {
  shape: rectangle
  style: {
    fill: "#eef2f7"
    stroke: "#475569"
    font-size: 14
  }
}

# ---- The loomcycle binary (center) ----

loomcycle: "loomcycle — single Go binary (~30 MB)\n1..N replicas (see cluster diagram, v0.12.x)" {
  style: {
    fill: "#fbfcfe"
    stroke: "#3b5998"
    stroke-width: 2
    border-radius: 12
    font-size: 16
    bold: true
  }

  direction: down

  surfaces: "Wire surfaces" {
    grid-columns: 4
    style.fill: "#f1f5f9"

    http_sse: "HTTP + SSE\n/v1/* endpoints" {style.fill: "#dbeafe"}
    grpc: "gRPC\n(metadata + streaming RPCs)" {style.fill: "#dbeafe"}
    webui: "React Web UI (/ui)" {style.fill: "#dbeafe"}
    mcp_stdio: "MCP server (stdio + HTTP)\nv0.8.15+ meta-tools" {
      style: {fill: "#c7d2fe"; bold: true}
    }
    gateway: "LLM Gateway (v0.11.0+)\n/v1/_llm/chat\n/v1/chat/completions (OpenAI-compat)\n/v1/embeddings" {
      style: {fill: "#c7d2fe"; bold: true}
    }
    a2a: "A2A server (v0.13.0, opt-in)\n/.well-known/agent-card.json\nREST · JSON-RPC · gRPC bindings" {
      style: {fill: "#c7d2fe"; bold: true}
    }
    webhooks: "Input webhooks (v0.14.0, opt-in)\nPOST /v1/_webhooks/{name}\nHMAC verify-before-parse" {
      style: {fill: "#c7d2fe"; bold: true}
    }
  }

  middleware: "Auth — bearer (constant-time) → RFC L principal: tenant · subject · scopes (v0.17.0)\nper-route HTTP + per-RPC gRPC scope gate · Concurrency semaphore · Cancel registry\nPer-tenant fairness (v0.10.1 · cluster v0.12.1)" {
    style: {
      fill: "#f8fafc"
      italic: true
      font-size: 13
    }
  }

  connector: "connector.Connector — 39-method Go interface (v0.8.15 → v0.17.x)\nHTTP server IMPLEMENTS · MCP / gRPC / future CLI CONSUME\nTS / Python adapters MIRROR (over HTTP wire)" {
    style: {
      fill: "#fce7f3"
      stroke: "#be185d"
      stroke-width: 2
      bold: true
      font-size: 13
    }
  }

  loop: "Agent loop\nmodel → tool_use → tool_result → model" {
    style: {
      fill: "#e0e7ff"
      stroke: "#4f46e5"
      stroke-width: 2
      bold: true
      font-size: 15
    }
  }

  context_plugins: "Context-transform plugins (v0.34.0 · RFC Z)\nper-turn chain at the loop seam — transforms a COPY of\nthe outbound (system + messages); canonical history untouched\nredact — outbound secret scrubbing (Tier-A env values + Tier-B patterns)\ncode-js exempt · compression planned (Phase 2)" {
    style: {
      fill: "#dcfce7"
      stroke: "#16a34a"
      stroke-width: 2
      font-size: 12
    }
  }

  dispatcher: "Tool dispatcher" {
    grid-columns: 2
    style.fill: "#fef3c7"

    builtins: "19 agent-facing built-in tools\nRead · Write · Edit · Grep · Glob\nNotebookEdit · HTTP · WebFetch\nWebSearch · Bash · Agent · Skill\nMemory (pluggable backend; +vector\nsearch; +add/recall layer v0.16.0)\nChannel · AgentDef · SkillDef\nEvaluation · Interruption · Context\n+ admin substrate Defs: MCPServerDef\nScheduleDef · WebhookDef · MemoryBackendDef\nA2AServerCardDef · A2AAgentDef" {
      style: {fill: "#fde68a"; font-size: 11}
    }
    mcp_layer: "MCP client transport\nstdio pool + Streamable HTTP\n(consumes external MCP servers)" {
      style: {fill: "#fde68a"; font-size: 12}
    }
    a2a_layer: "A2A client (v0.13.0)\nremote peers as\na2a__<peer>__<skill> tools" {
      style: {fill: "#fde68a"; font-size: 12}
    }
    subagents: "Sub-agent runner\n(Agent tool · parallel_spawn v0.11.8)\nctx-inherited policy" {
      style: {fill: "#fde68a"; font-size: 12}
    }
  }

  triggers: "Run triggers (besides /v1/runs)\nScheduler — ScheduleDef sweeper (v0.12.7)\nWebhook receiver (v0.14.0) · A2A inbound (v0.13.0)" {
    style: {fill: "#ede9fe"; stroke: "#7c3aed"; font-size: 12; italic: true}
  }

  store: "Store — SQLite (default) · Postgres (HA, + pgvector)\nsessions · runs(.replica_id v0.12.x · .tenant_id v0.17.0) · events · memory · channels\nagent_defs · skill_defs · mcp_server_defs · schedule_defs (v0.12.7)\nwebhook_defs (v0.14.0) · memory_backend_defs (v0.15.0) · operator_token_defs (v0.17.0) · *_active\nevaluations · interrupts · snapshots · process_samples\ndynamic_agents · memory_embeddings\nreplicas · user_quotas · runtime_state · hooks (v0.12.x)" {
    shape: cylinder
    style: {
      fill: "#fed7aa"
      stroke: "#ea580c"
      bold: true
      font-size: 12
    }
  }

  surfaces -> middleware: {style.stroke: "#94a3b8"}
  middleware -> connector: {style.stroke: "#94a3b8"}
  connector -> loop: "dispatch" {style.stroke: "#be185d"; style.stroke-width: 2}
  loop -> context_plugins: "outbound context\n(per turn, on a copy)" {style.stroke: "#16a34a"}
  loop -> dispatcher: "tool calls" {style.stroke: "#4f46e5"}
  loop -> store: "transcripts · events" {style.stroke: "#ea580c"}
  triggers -> loop: "fire run" {style.stroke: "#7c3aed"; style.stroke-dash: 4}
  dispatcher.subagents -> loop: "spawn child" {
    style.stroke: "#4f46e5"
    style.stroke-dash: 4
  }
}

# ---- Observability (v0.10.0, default OFF) ----

otel: "OpenTelemetry (v0.10.0)\nspans: loomcycle.run · iteration ·\nprovider.call · tool.call · mcp.call\nOTLP/HTTP · default OFF" {
  shape: rectangle
  style: {
    fill: "#fef3c7"
    stroke: "#a16207"
    border-radius: 8
    font-size: 12
    italic: true
  }
}

# ---- External services (right) ----

externals: "External" {
  style: {
    fill: "#fffbeb"
    stroke: "#d97706"
    border-radius: 12
  }
  direction: down

  providers: "Providers (pure HTTP — no SDKs; + 1 synthetic)" {
    grid-columns: 2
    style.fill: "#fef9c3"

    anthropic: "Anthropic" {style.fill: "#fde68a"}
    openai: "OpenAI" {style.fill: "#fde68a"}
    deepseek: "DeepSeek" {style.fill: "#fde68a"}
    gemini: "Gemini" {style.fill: "#fde68a"}
    ollama_cloud: "Ollama (cloud)" {style.fill: "#fde68a"}
    ollama_local: "Ollama (local)" {style.fill: "#fde68a"}
    anthropic_oauth_dev: "Anthropic OAuth\n(dev only · v0.11.10)" {
      style: {fill: "#fef9c3"; italic: true; stroke-dash: 4}
    }
    codejs: "code-js (v0.16.0) — SYNTHETIC\nin-process operator JS (goja)\nNO external call" {
      style: {fill: "#e0e7ff"; stroke: "#4f46e5"; italic: true; stroke-dash: 4}
    }
  }

  embedders: "Embedders (v0.9.0+)" {
    grid-columns: 3
    style.fill: "#fef9c3"

    openai_e: "OpenAI" {style.fill: "#fde68a"}
    gemini_e: "Gemini" {style.fill: "#fde68a"}
    voyage_e: "Voyage AI\n(via anthropic id, v0.10.2)" {style.fill: "#fde68a"}
  }

  mcp_servers: "External MCP servers\n(consumer-side typed tools)" {
    shape: cloud
    style: {fill: "#ede9fe"; stroke: "#7c3aed"}
  }

  a2a_peers: "Remote A2A peers (v0.13.0)\nin: call our skills · out: a2a__ tools" {
    shape: cloud
    style: {fill: "#ede9fe"; stroke: "#7c3aed"}
  }
}

# ---- Cross-layer edges ----

clients -> loomcycle.surfaces: "HTTP · gRPC · MCP" {style.stroke: "#475569"}
loomcycle.context_plugins -> externals.providers: "stream + tool_use\n(secrets redacted)" {
  style: {
    stroke: "#d97706"
    bold: true
  }
}
loomcycle.dispatcher.builtins -> externals.embedders: "Memory.search · vector" {
  style.stroke: "#d97706"
}
loomcycle.dispatcher.mcp_layer -> externals.mcp_servers: "JSON-RPC" {
  style.stroke: "#7c3aed"
}
loomcycle.dispatcher.a2a_layer -> externals.a2a_peers: "A2A client" {
  style.stroke: "#7c3aed"
}
externals.a2a_peers -> loomcycle.surfaces.a2a: "A2A inbound" {
  style: {stroke: "#7c3aed"; stroke-dash: 4}
}
loomcycle.loop -> otel: "spans (default OFF)" {
  style: {
    stroke: "#a16207"
    stroke-dash: 4
  }
}
