ARC-ADR-003 — No LLM Key in the Browser (CopilotKit Empty Adapter Security Boundary)¶
| Field | Value |
|---|---|
| ID | ARC-ADR-003 |
| Status | Accepted |
| Date | 2026-05-25 |
| Deciders | Architecture Review; accepted by hub owner 2026-05-25 |
| Supersedes | — |
| Superseded by | — |
| Tags | security, copilotkit, frontend-core, llm, browser, empty-adapter |
Context and Problem Statement¶
CopilotKit's Next.js integration supports multiple runtime adapter configurations. The CopilotRuntime can be configured with a direct LLM adapter (e.g., OpenAIAdapter, AnthropicAdapter) that calls an LLM provider directly from the Next.js API route — requiring that the LLM API key be present in the Next.js server environment or, worse, embedded in the browser bundle if the route is misconfigured.
For the AgentArmy CopilotKit initiative, the LLM (Cerebras) runs exclusively in middle-core (a separate Python FastAPI service). The Next.js runtime route in frontend-core serves only as a thin proxy — it should never hold or forward an LLM API key.
The decision to be made is: which CopilotKit adapter configuration must be used in frontend-core, and how is the no-LLM-key constraint enforced?
Decision Drivers¶
| # | Driver |
|---|---|
| D1 | LLM API keys (Cerebras) must never appear in the browser, in Next.js bundle analysis, or in any frontend-core environment variable. |
| D2 | The CopilotKit provider in frontend-core must route all LLM inference to middle-core — no direct LLM calls from the Next.js tier. |
| D3 | The constraint must be enforceable in CI (bundle analysis, environment variable audit) — not just a convention. |
| D4 | The configuration must be compatible with CopilotKit's standard App Router integration pattern. |
Considered Options¶
ExperimentalEmptyAdapter+remoteEndpointspointing at middle-core (proposed) — frontend-core uses CopilotKit's no-op adapter; all inference is handled by the remote middle-core endpoint.- Direct LLM adapter in frontend-core — frontend-core configures
OpenAIAdapterorAnthropicAdapterwith the Cerebras OpenAI-compatible base URL; middle-core is bypassed. - Shared Next.js + Python runtime via LangChain.js — collapse middle-core into a Next.js API route using LangChain.js; no separate Python service.
Decision Outcome¶
To be decided. The Architecture Review recommends Option 1 as the only option consistent with the four-layer architecture decision (middle-core holds the LLM key; frontend-core holds only the user JWT).
Proposed decision: Option 1 — ExperimentalEmptyAdapter + remoteEndpoints¶
app/api/copilotkit/route.tsusesCopilotRuntime({ remoteEndpoints: [{ url: process.env.MIDDLE_CORE_URL + '/copilotkit' }] })withExperimentalEmptyAdapter.MIDDLE_CORE_URLis the only new environment variable in frontend-core for this feature.- No LLM API key environment variable (
CEREBRAS_API_KEY,OPENAI_API_KEY, etc.) is present in frontend-core's.envor CI secrets. - CI bundle analysis (or a grep of the built output) confirms no LLM key pattern appears in any JS bundle.
Confirmation criteria¶
grep -r "CEREBRAS\|sk-\|llm.*key" .next/returns no matches after build.- No LLM API key appears in
process.envat the Next.js API route level. - The CopilotKit provider successfully routes inference to middle-core using only
MIDDLE_CORE_URL.
Affected Layers / Repos¶
| Layer | Repo | Impact |
|---|---|---|
| frontend-core | nickpclarke/frontend-core | Must use ExperimentalEmptyAdapter; must not add any LLM key to env; issues #12, #13 |
| middle-core | nickpclarke/middle-core | Holds CEREBRAS_API_KEY; exposes /copilotkit as the remote endpoint; issue #17 |
| backend-core | nickpclarke/backend-core | No impact |
Pros and Cons of the Options¶
Option 1 — ExperimentalEmptyAdapter (proposed)¶
Pros: - No LLM key in frontend-core by construction — the adapter has no LLM call capability. - Consistent with the four-layer architectural decision (LLM lives in middle-core). - Auditable: bundle analysis and env audit are trivial checks.
Cons:
- ExperimentalEmptyAdapter is marked experimental — API may change in future CopilotKit versions; requires a version pin and upgrade tracking.
- If middle-core is unavailable, the copilot is completely non-functional (no graceful degradation to a browser-side LLM).
Option 2 — Direct LLM adapter in frontend-core¶
Pros: Simpler deployment (one fewer service).
Cons: - LLM API key must be present in frontend-core environment — violates D1. - Collapses the security boundary between the presentation tier and the AI tier. - Bypasses middle-core, making tool-calling and LangGraph agent logic impossible.
Option 3 — Collapse middle-core into Next.js¶
Pros: Single deployment artifact.
Cons: - LLM key in Next.js environment — violates D1. - Requires LangGraph + LangChain.js (JavaScript port) — different ecosystem, different testing story. - Violates the four-layer architecture decision made with the user.
Positive Consequences (if Option 1 accepted)¶
- LLM key surface area is minimized to the middle-core service (one secret, one location).
- Frontend-core can be deployed as a fully public Next.js app without LLM key rotation risk.
Negative Consequences (if Option 1 accepted)¶
ExperimentalEmptyAdapterAPI stability must be monitored across CopilotKit upgrades.- middle-core availability is a hard dependency for any copilot functionality.
Related Decisions¶
- ARC-ADR-002: JWT-forwarding auth contract — the companion security decision (user credential handling).
- ARC-ADR-004: LLM provider = Cerebras — governs which LLM key middle-core holds.
Revision History¶
| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | 2026-05-25 | Scrum Master (hub decomposition) | Initial proposed ADR stub |