merge: integrate forge dispatch + mcp changes with AX compliance sweep
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
commit
db6d06ae2b
17 changed files with 153 additions and 21 deletions
|
|
@ -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"
|
||||
|
|
|
|||
11
claude/core/.mcp.json
Normal file
11
claude/core/.mcp.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"core": {
|
||||
"type": "stdio",
|
||||
"command": "core-agent",
|
||||
"args": ["mcp"],
|
||||
"env": {
|
||||
"MONITOR_INTERVAL": "10s",
|
||||
"CORE_AGENT_DISPATCH": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,8 @@
|
|||
"command": "core-agent",
|
||||
"args": ["mcp"],
|
||||
"env": {
|
||||
"MONITOR_INTERVAL": "15s"
|
||||
"MONITOR_INTERVAL": "15s",
|
||||
"CORE_AGENT_DISPATCH": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
claude/core/000mcp.json
Normal file
13
claude/core/000mcp.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"core": {
|
||||
"type": "stdio",
|
||||
"command": "core-agent",
|
||||
"args": ["mcp"],
|
||||
"env": {
|
||||
"MONITOR_INTERVAL": "15s",
|
||||
"CORE_AGENT_DISPATCH": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
name: remember
|
||||
description: Save a fact or decision to OpenBrain for persistence across sessions
|
||||
args: <fact to remember>
|
||||
allowed-tools: ["mcp__core__brain_remember"]
|
||||
allowed-tools: ["mcp__plugin_agent_agent__brain_remember"]
|
||||
---
|
||||
|
||||
# Remember
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
2
go.mod
2
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue