diff --git a/cmd/hyperguild/README.md b/cmd/hyperguild/README.md index 7fdf59b..bc73922 100644 --- a/cmd/hyperguild/README.md +++ b/cmd/hyperguild/README.md @@ -115,9 +115,10 @@ Flags: Modes: - **cloud** — brain MCP only. Claude Code with no routing. -- **client-local** — brain + routing placeholder. The routing entry's - URL points at `koala:30310/mcp`; a `_routing_pending` field marks it - as awaiting Plan 6 of the hyperguild migration. +- **client-local** — brain + routing pod. The `routing` entry points at + `koala:30310/mcp` (the routing pod, deployed in Plan 6). The + `X-Hyperguild-Mode: client-local` header is forward-compat for future + modes; the pod treats absent or unknown values as `client-local`. - **sovereign** — brain only, with a `_mode_note` explaining that this mode primarily uses Crush + LiteLLM and the `.mcp.json` is a Claude Code fallback for emergency offline use. diff --git a/cmd/hyperguild/mode.go b/cmd/hyperguild/mode.go index db4a583..51cd3a9 100644 --- a/cmd/hyperguild/mode.go +++ b/cmd/hyperguild/mode.go @@ -78,9 +78,11 @@ func modeClientLocal(brainURL string) map[string]any { "description": "Brain MCP — knowledge query, write, ingestion, session log", }, "routing": map[string]any{ - "url": "http://koala:30310/mcp", - "description": "Mode 2 routing pod — routes skill calls to LiteLLM/local", - "_routing_pending": "Plan 6 — routing pod not deployed yet; this URL is a placeholder", + "url": "http://koala:30310/mcp", + "description": "Mode 2 routing pod — routes skill calls to LiteLLM/local", + "headers": map[string]any{ + "X-Hyperguild-Mode": "client-local", + }, }, }, } diff --git a/cmd/hyperguild/mode_test.go b/cmd/hyperguild/mode_test.go index 43bbc5f..077b2c3 100644 --- a/cmd/hyperguild/mode_test.go +++ b/cmd/hyperguild/mode_test.go @@ -39,7 +39,7 @@ func TestRunMode_Cloud_Default(t *testing.T) { assert.NotContains(t, got, "_mode_note") } -func TestRunMode_ClientLocal_HasRoutingPlaceholder(t *testing.T) { +func TestRunMode_ClientLocal_HasRoutingEntry(t *testing.T) { dir := t.TempDir() outPath := filepath.Join(dir, ".mcp.json") t.Setenv("BRAIN_URL", "http://koala:30330") @@ -54,7 +54,32 @@ func TestRunMode_ClientLocal_HasRoutingPlaceholder(t *testing.T) { require.Contains(t, servers, "routing") routing := servers["routing"].(map[string]any) - assert.Contains(t, routing, "_routing_pending") + assert.NotContains(t, routing, "_routing_pending", "placeholder should be removed once Plan 6 ships") + + headers, ok := routing["headers"].(map[string]any) + require.True(t, ok, "routing entry should have headers block") + assert.Equal(t, "client-local", headers["X-Hyperguild-Mode"]) +} + +func TestModeClientLocalHasRoutingHeader(t *testing.T) { + tmp := t.TempDir() + "/mcp.json" + out := &bytes.Buffer{} + stderr := &bytes.Buffer{} + require.NoError(t, runMode(context.Background(), []string{"client-local", "--out", tmp}, nil, out, stderr)) + + body, err := os.ReadFile(tmp) + require.NoError(t, err) + var doc map[string]any + require.NoError(t, json.Unmarshal(body, &doc)) + + servers := doc["mcpServers"].(map[string]any) + routing := servers["routing"].(map[string]any) + assert.Equal(t, "http://koala:30310/mcp", routing["url"]) + assert.NotContains(t, routing, "_routing_pending", "placeholder should be removed once Plan 6 ships") + + headers, ok := routing["headers"].(map[string]any) + require.True(t, ok, "routing entry should have headers block") + assert.Equal(t, "client-local", headers["X-Hyperguild-Mode"]) } func TestRunMode_Sovereign_HasModeNote(t *testing.T) {