From e8862e26be42970c2cd86200ba40d051eb1f21b4 Mon Sep 17 00:00:00 2001 From: Snider Date: Wed, 8 Apr 2026 17:17:33 +0100 Subject: [PATCH 1/2] feat(dispatch): LEM profiles + native Claude agents - Add isLEMProfile(): codex:lemmy/lemer/lemma/lemrd use --profile not --model - Add isNativeAgent(): Claude agents run natively (not in Docker) - Update localAgentCommandScript for LEM profile support - 12 new tests (Good/Bad/Ugly for profiles, native agent, codex variants) Co-Authored-By: Virgil --- claude/core/{mcp.json => .mcp.json} | 2 +- pkg/agentic/dispatch.go | 42 +++++++++++++++++-- pkg/agentic/logic_test.go | 62 +++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) rename claude/core/{mcp.json => .mcp.json} (91%) diff --git a/claude/core/mcp.json b/claude/core/.mcp.json similarity index 91% rename from claude/core/mcp.json rename to claude/core/.mcp.json index a0d4bcd..e8ca239 100644 --- a/claude/core/mcp.json +++ b/claude/core/.mcp.json @@ -1,6 +1,6 @@ { "mcpServers": { - "core": { + "agent": { "type": "stdio", "command": "core-agent", "args": ["mcp"], diff --git a/pkg/agentic/dispatch.go b/pkg/agentic/dispatch.go index 80cc7a2..c1d27b7 100644 --- a/pkg/agentic/dispatch.go +++ b/pkg/agentic/dispatch.go @@ -105,7 +105,11 @@ func agentCommandResult(agent, prompt string) core.Result { "-o", "../.meta/agent-codex.log", } if model != "" { - args = append(args, "--model", model) + if isLEMProfile(model) { + args = append(args, "--profile", model) + } else { + args = append(args, "--model", model) + } } args = append(args, prompt) return core.Result{Value: agentCommandResultValue{command: "codex", args: args}, OK: true} @@ -142,11 +146,41 @@ func agentCommandResult(agent, prompt string) core.Result { } } +// isNativeAgent returns true if the agent should run natively (not in Docker). +// Claude agents need direct filesystem access, MCP tools, and native binary execution. +// +// isNativeAgent("claude") // true +// isNativeAgent("claude:opus") // true +// isNativeAgent("codex") // false (runs in Docker) +func isNativeAgent(agent string) bool { + parts := core.SplitN(agent, ":", 2) + return parts[0] == "claude" +} + +// isLEMProfile returns true if the model name is a known LEM profile +// (lemer, lemma, lemmy, lemrd) configured in codex config.toml. +// +// isLEMProfile("lemmy") // true +// isLEMProfile("gpt-5.4") // false +func isLEMProfile(model string) bool { + switch model { + case "lemer", "lemma", "lemmy", "lemrd": + return true + default: + return false + } +} + // localAgentCommandScript("devstral-24b", "Review the last 2 commits") func localAgentCommandScript(model, prompt string) string { builder := core.NewBuilder() builder.WriteString("socat TCP-LISTEN:11434,fork,reuseaddr TCP:host.docker.internal:11434 & sleep 0.5") - builder.WriteString(" && codex exec --dangerously-bypass-approvals-and-sandbox --oss --local-provider ollama -m ") + builder.WriteString(" && codex exec --dangerously-bypass-approvals-and-sandbox") + if isLEMProfile(model) { + builder.WriteString(" --profile ") + } else { + builder.WriteString(" --oss --local-provider ollama -m ") + } builder.WriteString(shellQuote(model)) builder.WriteString(" -o ../.meta/agent-codex.log ") builder.WriteString(shellQuote(prompt)) @@ -373,7 +407,9 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, workspaceDir string) (int, str fs.Delete(WorkspaceBlockedPath(workspaceDir)) - command, args = containerCommand(command, args, workspaceDir, metaDir) + if !isNativeAgent(agent) { + command, args = containerCommand(command, args, workspaceDir, metaDir) + } processResult := s.Core().Service("process") if !processResult.OK { diff --git a/pkg/agentic/logic_test.go b/pkg/agentic/logic_test.go index 38deeb4..bdd06ea 100644 --- a/pkg/agentic/logic_test.go +++ b/pkg/agentic/logic_test.go @@ -106,6 +106,68 @@ func TestDispatch_LocalAgentCommandScript_Good_ShellQuoting(t *testing.T) { assert.Contains(t, script, "'can'\\''t break quoting'") } +func TestDispatch_AgentCommand_Good_CodexLEMProfile(t *testing.T) { + cmd, args, err := agentCommand("codex:lemmy", "implement the scorer") + require.NoError(t, err) + assert.Equal(t, "codex", cmd) + assert.Contains(t, args, "--profile") + assert.Contains(t, args, "lemmy") + assert.NotContains(t, args, "--model") +} + +func TestDispatch_AgentCommand_Good_CodexLemer(t *testing.T) { + cmd, args, err := agentCommand("codex:lemer", "add docs") + require.NoError(t, err) + assert.Equal(t, "codex", cmd) + assert.Contains(t, args, "--profile") + assert.Contains(t, args, "lemer") +} + +func TestDispatch_AgentCommand_Good_CodexLemrd(t *testing.T) { + cmd, args, err := agentCommand("codex:lemrd", "review code") + require.NoError(t, err) + assert.Equal(t, "codex", cmd) + assert.Contains(t, args, "--profile") + assert.Contains(t, args, "lemrd") +} + +func TestDispatch_IsLEMProfile_Good(t *testing.T) { + assert.True(t, isLEMProfile("lemer")) + assert.True(t, isLEMProfile("lemma")) + assert.True(t, isLEMProfile("lemmy")) + assert.True(t, isLEMProfile("lemrd")) +} + +func TestDispatch_IsLEMProfile_Bad(t *testing.T) { + assert.False(t, isLEMProfile("gpt-5.4")) + assert.False(t, isLEMProfile("gemini-2.5-flash")) + assert.False(t, isLEMProfile("")) +} + +func TestDispatch_IsLEMProfile_Ugly(t *testing.T) { + assert.False(t, isLEMProfile("Lemmy")) + assert.False(t, isLEMProfile("LEMRD")) + assert.False(t, isLEMProfile("lem")) +} + +func TestDispatch_IsNativeAgent_Good(t *testing.T) { + assert.True(t, isNativeAgent("claude")) + assert.True(t, isNativeAgent("claude:opus")) + assert.True(t, isNativeAgent("claude:haiku")) +} + +func TestDispatch_IsNativeAgent_Bad(t *testing.T) { + assert.False(t, isNativeAgent("codex")) + assert.False(t, isNativeAgent("codex:gpt-5.4")) + assert.False(t, isNativeAgent("gemini")) +} + +func TestDispatch_IsNativeAgent_Ugly(t *testing.T) { + assert.False(t, isNativeAgent("")) + assert.False(t, isNativeAgent("codex:lemmy")) + assert.False(t, isNativeAgent("local:mistral")) +} + func TestDispatch_AgentCommand_Bad_Unknown(t *testing.T) { cmd, args, err := agentCommand("robot-from-the-future", "take over") assert.Error(t, err) From 7a531d1112a9ba1c0a0b7d835e516798f8ae51c0 Mon Sep 17 00:00:00 2001 From: Snider Date: Wed, 8 Apr 2026 19:17:21 +0100 Subject: [PATCH 2/2] perf(mcp): reduce tool count from 189 to 82 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gate non-essential MCP tools behind CORE_MCP_FULL=1 env var. Core factory tools (dispatch, status, plan, issue, PR, scan, mirror, watch, brain, files) always registered. Extended tools (session, sprint, state, phase, task, template, message, content, platform, epic, remote, review-queue, setup, metrics, RAG, webview) only when full mode enabled. 189 → 82 tools in default mode. Fixes slow MCP startup and tool registration timeout in Claude Code. Co-Authored-By: Virgil --- claude/core/.claude-plugin/plugin.json | 4 ++-- claude/core/.mcp.json | 15 +++++++------ claude/core/000.mcp.json | 13 ++++++++++++ claude/core/000mcp.json | 13 ++++++++++++ claude/core/commands/dispatch.md | 2 +- claude/core/commands/recall.md | 2 +- claude/core/commands/remember.md | 2 +- claude/core/commands/review.md | 2 +- claude/core/commands/scan.md | 2 +- claude/core/commands/status.md | 2 +- claude/core/commands/sweep.md | 2 +- claude/core/skills/orchestrate.md | 2 +- go.mod | 4 +++- pkg/agentic/prep.go | 29 ++++++++++++++------------ pkg/agentic/prep_test.go | 1 + 15 files changed, 63 insertions(+), 32 deletions(-) create mode 100644 claude/core/000.mcp.json create mode 100644 claude/core/000mcp.json diff --git a/claude/core/.claude-plugin/plugin.json b/claude/core/.claude-plugin/plugin.json index 4c6015b..7803ab0 100644 --- a/claude/core/.claude-plugin/plugin.json +++ b/claude/core/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { - "name": "core", + "name": "agent", "description": "Core agent platform — dispatch (local + remote), verify+merge, CodeRabbit/Codex review queue, GitHub mirror, cross-agent messaging, OpenBrain integration, inbox notifications", - "version": "0.15.0", + "version": "0.18.0", "author": { "name": "Lethean Community", "email": "hello@lethean.io" diff --git a/claude/core/.mcp.json b/claude/core/.mcp.json index e8ca239..ec338c8 100644 --- a/claude/core/.mcp.json +++ b/claude/core/.mcp.json @@ -1,12 +1,11 @@ { - "mcpServers": { - "agent": { - "type": "stdio", - "command": "core-agent", - "args": ["mcp"], - "env": { - "MONITOR_INTERVAL": "15s" - } + "core": { + "type": "stdio", + "command": "core-agent", + "args": ["mcp"], + "env": { + "MONITOR_INTERVAL": "10s", + "CORE_AGENT_DISPATCH": "1" } } } diff --git a/claude/core/000.mcp.json b/claude/core/000.mcp.json new file mode 100644 index 0000000..3f2bff7 --- /dev/null +++ b/claude/core/000.mcp.json @@ -0,0 +1,13 @@ +{ + "mcpServers": { + "core": { + "type": "stdio", + "command": "core-agent", + "args": ["mcp"], + "env": { + "MONITOR_INTERVAL": "15s", + "CORE_AGENT_DISPATCH": "1" + } + } + } +} diff --git a/claude/core/000mcp.json b/claude/core/000mcp.json new file mode 100644 index 0000000..3f2bff7 --- /dev/null +++ b/claude/core/000mcp.json @@ -0,0 +1,13 @@ +{ + "mcpServers": { + "core": { + "type": "stdio", + "command": "core-agent", + "args": ["mcp"], + "env": { + "MONITOR_INTERVAL": "15s", + "CORE_AGENT_DISPATCH": "1" + } + } + } +} diff --git a/claude/core/commands/dispatch.md b/claude/core/commands/dispatch.md index 397c2bf..a8e1342 100644 --- a/claude/core/commands/dispatch.md +++ b/claude/core/commands/dispatch.md @@ -22,7 +22,7 @@ arguments: Dispatch a subagent to work on `$ARGUMENTS.repo` with task: `$ARGUMENTS.task` -Use the `mcp__core__agentic_dispatch` tool with: +Use the `mcp__plugin_agent_agent__agentic_dispatch` tool with: - repo: $ARGUMENTS.repo - task: $ARGUMENTS.task - agent: $ARGUMENTS.agent diff --git a/claude/core/commands/recall.md b/claude/core/commands/recall.md index 487b4cd..4db99ef 100644 --- a/claude/core/commands/recall.md +++ b/claude/core/commands/recall.md @@ -11,7 +11,7 @@ arguments: description: Filter by type (decision, plan, convention, architecture, observation, fact) --- -Use the `mcp__core__brain_recall` tool with: +Use the `mcp__plugin_agent_agent__brain_recall` tool with: - query: $ARGUMENTS.query - top_k: 5 - filter with project and type if provided diff --git a/claude/core/commands/remember.md b/claude/core/commands/remember.md index 2466850..581c84d 100644 --- a/claude/core/commands/remember.md +++ b/claude/core/commands/remember.md @@ -2,7 +2,7 @@ name: remember description: Save a fact or decision to OpenBrain for persistence across sessions args: -allowed-tools: ["mcp__core__brain_remember"] +allowed-tools: ["mcp__plugin_agent_agent__brain_remember"] --- # Remember diff --git a/claude/core/commands/review.md b/claude/core/commands/review.md index 73a2a16..d0a7bc5 100644 --- a/claude/core/commands/review.md +++ b/claude/core/commands/review.md @@ -6,7 +6,7 @@ arguments: description: Workspace name (e.g. go-html-1773592564). If omitted, shows all completed. --- -If no workspace specified, use `mcp__core__agentic_status` to list all workspaces, then show only completed ones with a summary table. +If no workspace specified, use `mcp__plugin_agent_agent__agentic_status` to list all workspaces, then show only completed ones with a summary table. If workspace specified: 1. Read the agent log file: `.core/workspace/{workspace}/agent-*.log` diff --git a/claude/core/commands/scan.md b/claude/core/commands/scan.md index b00d51a..3c3974d 100644 --- a/claude/core/commands/scan.md +++ b/claude/core/commands/scan.md @@ -7,6 +7,6 @@ arguments: default: core --- -Use the `mcp__core__agentic_scan` tool with org: $ARGUMENTS.org +Use the `mcp__plugin_agent_agent__agentic_scan` tool with org: $ARGUMENTS.org Show results as a table with columns: Repo, Issue #, Title, Labels. diff --git a/claude/core/commands/status.md b/claude/core/commands/status.md index 2a912a5..6b13d1e 100644 --- a/claude/core/commands/status.md +++ b/claude/core/commands/status.md @@ -3,7 +3,7 @@ name: status description: Show status of all agent workspaces (running, completed, blocked, failed) --- -Use the `mcp__core__agentic_status` tool to list all agent workspaces. +Use the `mcp__plugin_agent_agent__agentic_status` tool to list all agent workspaces. Show results as a table with columns: Name, Status, Agent, Repo, Task, Age. diff --git a/claude/core/commands/sweep.md b/claude/core/commands/sweep.md index 7cff6e0..256ca3e 100644 --- a/claude/core/commands/sweep.md +++ b/claude/core/commands/sweep.md @@ -15,7 +15,7 @@ arguments: Run a batch conventions or security audit across the Go ecosystem. 1. If repos not specified, find all Go repos in ~/Code/core/ that have a go.mod -2. For each repo, call `mcp__core__agentic_dispatch` with: +2. For each repo, call `mcp__plugin_agent_agent__agentic_dispatch` with: - repo: {repo name} - task: "{template} audit - UK English, error handling, interface checks, import aliasing" - agent: $ARGUMENTS.agent diff --git a/claude/core/skills/orchestrate.md b/claude/core/skills/orchestrate.md index 5af3aed..d67557c 100644 --- a/claude/core/skills/orchestrate.md +++ b/claude/core/skills/orchestrate.md @@ -48,7 +48,7 @@ Output a task list with: task name, persona, template, estimated complexity. For each task from Stage 1, dispatch an agent. Prefer MCP tools if available: ``` -mcp__core__agentic_dispatch(repo, task, agent, template, persona) +mcp__plugin_agent_agent__agentic_dispatch(repo, task, agent, template, persona) ``` If MCP is unavailable, dispatch locally: diff --git a/go.mod b/go.mod index d251952..6472e15 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( dappco.re/go/core v0.8.0-alpha.1 dappco.re/go/core/api v0.3.0 dappco.re/go/core/forge v0.3.1 - dappco.re/go/core/process v0.5.0 + dappco.re/go/core/process v0.5.1 dappco.re/go/core/ws v0.4.0 dappco.re/go/mcp v0.5.1 github.com/gin-gonic/gin v1.12.0 @@ -126,3 +126,5 @@ require ( google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.11 // indirect ) + +replace dappco.re/go/mcp => ../mcp diff --git a/pkg/agentic/prep.go b/pkg/agentic/prep.go index 3db2262..c60fd32 100644 --- a/pkg/agentic/prep.go +++ b/pkg/agentic/prep.go @@ -421,11 +421,11 @@ func (s *PrepSubsystem) SetCore(c *core.Core) { // subsystem := agentic.NewPrep() // subsystem.RegisterTools(svc) func (s *PrepSubsystem) RegisterTools(svc *coremcp.Service) { + // Core tools — always registered (the factory essentials) mcp.AddTool(svc.Server(), &mcp.Tool{ Name: "agentic_prep_workspace", Description: "Prepare an agent workspace: clone repo, create branch, build prompt with context.", }, s.prepWorkspace) - s.registerDispatchTool(svc) s.registerStatusTool(svc) s.registerResumeTool(svc) @@ -437,34 +437,37 @@ func (s *PrepSubsystem) RegisterTools(svc *coremcp.Service) { s.registerCreatePRTool(svc) s.registerListPRsTool(svc) s.registerClosePRTool(svc) - s.registerEpicTool(svc) s.registerMirrorTool(svc) + s.registerShutdownTools(svc) + s.registerPlanTools(svc) + s.registerWatchTool(svc) + s.registerIssueTools(svc) + s.registerPRTools(svc) + mcp.AddTool(svc.Server(), &mcp.Tool{ + Name: "agentic_scan", + Description: "Scan Forge repos for open issues with actionable labels (agentic, help-wanted, bug).", + }, s.scan) + + // Extended tools — only when CORE_MCP_FULL=1 + if core.Env("CORE_MCP_FULL") != "1" { + return + } + s.registerEpicTool(svc) s.registerRemoteDispatchTool(svc) s.registerRemoteStatusTool(svc) s.registerReviewQueueTool(svc) s.registerPlatformTools(svc) - s.registerShutdownTools(svc) s.registerSessionTools(svc) s.registerStateTools(svc) s.registerPhaseTools(svc) s.registerTaskTools(svc) s.registerPromptTools(svc) s.registerTemplateTools(svc) - s.registerIssueTools(svc) s.registerMessageTools(svc) s.registerSprintTools(svc) - s.registerPRTools(svc) s.registerContentTools(svc) s.registerLanguageTools(svc) s.registerSetupTool(svc) - - mcp.AddTool(svc.Server(), &mcp.Tool{ - Name: "agentic_scan", - Description: "Scan Forge repos for open issues with actionable labels (agentic, help-wanted, bug).", - }, s.scan) - - s.registerPlanTools(svc) - s.registerWatchTool(svc) } // subsystem := agentic.NewPrep() diff --git a/pkg/agentic/prep_test.go b/pkg/agentic/prep_test.go index 66dc1fd..d11005c 100644 --- a/pkg/agentic/prep_test.go +++ b/pkg/agentic/prep_test.go @@ -693,6 +693,7 @@ func TestPrep_OnStartup_Good_RegistersPlatformCommandAlias(t *testing.T) { } func TestPrep_RegisterTools_Good_RegistersCompletionTool(t *testing.T) { + t.Setenv("CORE_MCP_FULL", "1") svc, err := coremcp.New(coremcp.Options{Unrestricted: true}) require.NoError(t, err)