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 new file mode 100644 index 0000000..ec338c8 --- /dev/null +++ b/claude/core/.mcp.json @@ -0,0 +1,11 @@ +{ + "core": { + "type": "stdio", + "command": "core-agent", + "args": ["mcp"], + "env": { + "MONITOR_INTERVAL": "10s", + "CORE_AGENT_DISPATCH": "1" + } + } +} diff --git a/claude/core/mcp.json b/claude/core/000.mcp.json similarity index 67% rename from claude/core/mcp.json rename to claude/core/000.mcp.json index a0d4bcd..3f2bff7 100644 --- a/claude/core/mcp.json +++ b/claude/core/000.mcp.json @@ -5,7 +5,8 @@ "command": "core-agent", "args": ["mcp"], "env": { - "MONITOR_INTERVAL": "15s" + "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 30acd4b..a85bd9d 100644 --- a/go.mod +++ b/go.mod @@ -125,3 +125,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/dispatch.go b/pkg/agentic/dispatch.go index 37b758b..a8a46e9 100644 --- a/pkg/agentic/dispatch.go +++ b/pkg/agentic/dispatch.go @@ -119,7 +119,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} @@ -156,11 +160,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)) @@ -403,8 +437,6 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, workspaceDir string) (int, str fs.Delete(WorkspaceBlockedPath(workspaceDir)) - // Native agents (claude, coderabbit) run directly on the host — no Docker. - // Docker agents (codex, gemini, local) get containerised for isolation. if !isNativeAgent(agent) { command, args = containerCommand(command, args, workspaceDir, metaDir) } 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) diff --git a/pkg/agentic/prep.go b/pkg/agentic/prep.go index 60a83a7..839158f 100644 --- a/pkg/agentic/prep.go +++ b/pkg/agentic/prep.go @@ -420,11 +420,10 @@ func (s *PrepSubsystem) SetCore(c *core.Core) { // subsystem := agentic.NewPrep() // subsystem.RegisterTools(svc) func (s *PrepSubsystem) RegisterTools(svc *coremcp.Service) { - coremcp.AddToolRecorded(svc, svc.Server(), "agentic", &mcp.Tool{ + 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) @@ -436,23 +435,34 @@ 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) 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)