From e8862e26be42970c2cd86200ba40d051eb1f21b4 Mon Sep 17 00:00:00 2001 From: Snider Date: Wed, 8 Apr 2026 17:17:33 +0100 Subject: [PATCH] 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)