2026-03-17 17:45:04 +00:00
|
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
|
|
|
|
|
|
package agentic
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-03-22 03:41:07 +00:00
|
|
|
core "dappco.re/go/core"
|
2026-03-17 17:45:04 +00:00
|
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-31 04:33:36 +00:00
|
|
|
// input := agentic.RemoteDispatchInput{Host: "charon", Repo: "go-io", Task: "Run the review queue"}
|
2026-03-17 17:45:04 +00:00
|
|
|
type RemoteDispatchInput struct {
|
2026-03-31 05:28:26 +00:00
|
|
|
Host string `json:"host"`
|
|
|
|
|
Repo string `json:"repo"`
|
|
|
|
|
Task string `json:"task"`
|
|
|
|
|
Agent string `json:"agent,omitempty"`
|
|
|
|
|
Template string `json:"template,omitempty"`
|
|
|
|
|
Persona string `json:"persona,omitempty"`
|
|
|
|
|
Org string `json:"org,omitempty"`
|
|
|
|
|
Variables map[string]string `json:"variables,omitempty"`
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 04:33:36 +00:00
|
|
|
// out := agentic.RemoteDispatchOutput{Success: true, Host: "charon", Repo: "go-io", Agent: "claude:opus"}
|
2026-03-17 17:45:04 +00:00
|
|
|
type RemoteDispatchOutput struct {
|
|
|
|
|
Success bool `json:"success"`
|
|
|
|
|
Host string `json:"host"`
|
|
|
|
|
Repo string `json:"repo"`
|
|
|
|
|
Agent string `json:"agent"`
|
|
|
|
|
WorkspaceDir string `json:"workspace_dir,omitempty"`
|
|
|
|
|
PID int `json:"pid,omitempty"`
|
|
|
|
|
Error string `json:"error,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *PrepSubsystem) registerRemoteDispatchTool(server *mcp.Server) {
|
|
|
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
|
|
|
Name: "agentic_dispatch_remote",
|
|
|
|
|
Description: "Dispatch a task to a remote core-agent (e.g. Charon). The remote agent preps a workspace and spawns the task locally on its hardware.",
|
|
|
|
|
}, s.dispatchRemote)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *PrepSubsystem) dispatchRemote(ctx context.Context, _ *mcp.CallToolRequest, input RemoteDispatchInput) (*mcp.CallToolResult, RemoteDispatchOutput, error) {
|
|
|
|
|
if input.Host == "" {
|
2026-03-22 03:41:07 +00:00
|
|
|
return nil, RemoteDispatchOutput{}, core.E("dispatchRemote", "host is required", nil)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
if input.Repo == "" {
|
2026-03-22 03:41:07 +00:00
|
|
|
return nil, RemoteDispatchOutput{}, core.E("dispatchRemote", "repo is required", nil)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
if input.Task == "" {
|
2026-03-22 03:41:07 +00:00
|
|
|
return nil, RemoteDispatchOutput{}, core.E("dispatchRemote", "task is required", nil)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 06:23:07 +00:00
|
|
|
client := NewRemoteClient(input.Host)
|
2026-03-17 17:45:04 +00:00
|
|
|
|
|
|
|
|
callParams := map[string]any{
|
|
|
|
|
"repo": input.Repo,
|
|
|
|
|
"task": input.Task,
|
|
|
|
|
}
|
|
|
|
|
if input.Agent != "" {
|
|
|
|
|
callParams["agent"] = input.Agent
|
|
|
|
|
}
|
|
|
|
|
if input.Template != "" {
|
|
|
|
|
callParams["template"] = input.Template
|
|
|
|
|
}
|
|
|
|
|
if input.Persona != "" {
|
|
|
|
|
callParams["persona"] = input.Persona
|
|
|
|
|
}
|
|
|
|
|
if input.Org != "" {
|
|
|
|
|
callParams["org"] = input.Org
|
|
|
|
|
}
|
|
|
|
|
if len(input.Variables) > 0 {
|
|
|
|
|
callParams["variables"] = input.Variables
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 06:23:07 +00:00
|
|
|
output := RemoteDispatchOutput{
|
|
|
|
|
Success: true,
|
|
|
|
|
Host: input.Host,
|
|
|
|
|
Repo: input.Repo,
|
|
|
|
|
Agent: input.Agent,
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 06:23:07 +00:00
|
|
|
sessionID, err := client.Initialize(ctx)
|
|
|
|
|
if err != nil {
|
2026-03-17 17:45:04 +00:00
|
|
|
return nil, RemoteDispatchOutput{
|
|
|
|
|
Host: input.Host,
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
Error: core.Sprintf("init failed: %v", err),
|
2026-03-22 03:41:07 +00:00
|
|
|
}, core.E("dispatchRemote", "MCP initialize failed", err)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
2026-03-30 14:01:43 +00:00
|
|
|
|
2026-04-02 06:23:07 +00:00
|
|
|
result, err := client.Call(ctx, sessionID, client.ToolCallBody(1, "agentic_dispatch", callParams))
|
|
|
|
|
if err != nil {
|
2026-03-17 17:45:04 +00:00
|
|
|
return nil, RemoteDispatchOutput{
|
|
|
|
|
Host: input.Host,
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
Error: core.Sprintf("call failed: %v", err),
|
2026-03-22 03:41:07 +00:00
|
|
|
}, core.E("dispatchRemote", "tool call failed", err)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:41:45 +00:00
|
|
|
var rpcResponse struct {
|
2026-03-17 17:45:04 +00:00
|
|
|
Result struct {
|
|
|
|
|
Content []struct {
|
|
|
|
|
Text string `json:"text"`
|
|
|
|
|
} `json:"content"`
|
|
|
|
|
} `json:"result"`
|
|
|
|
|
Error *struct {
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
} `json:"error"`
|
|
|
|
|
}
|
2026-03-30 21:41:45 +00:00
|
|
|
if r := core.JSONUnmarshal(result, &rpcResponse); r.OK {
|
|
|
|
|
if rpcResponse.Error != nil {
|
2026-03-17 17:45:04 +00:00
|
|
|
output.Success = false
|
2026-03-30 21:41:45 +00:00
|
|
|
output.Error = rpcResponse.Error.Message
|
|
|
|
|
} else if len(rpcResponse.Result.Content) > 0 {
|
2026-03-17 17:45:04 +00:00
|
|
|
var dispatchOut DispatchOutput
|
2026-03-30 21:41:45 +00:00
|
|
|
if r := core.JSONUnmarshalString(rpcResponse.Result.Content[0].Text, &dispatchOut); r.OK {
|
2026-03-21 17:46:04 +00:00
|
|
|
output.Success = dispatchOut.Success
|
2026-03-17 17:45:04 +00:00
|
|
|
output.WorkspaceDir = dispatchOut.WorkspaceDir
|
|
|
|
|
output.PID = dispatchOut.PID
|
|
|
|
|
output.Agent = dispatchOut.Agent
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil, output, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 04:33:36 +00:00
|
|
|
// addr := resolveHost("charon") // "10.69.69.165:9101"
|
2026-03-17 17:45:04 +00:00
|
|
|
func resolveHost(host string) string {
|
2026-04-02 09:12:49 +00:00
|
|
|
host = core.Trim(host)
|
2026-03-17 17:45:04 +00:00
|
|
|
aliases := map[string]string{
|
|
|
|
|
"charon": "10.69.69.165:9101",
|
|
|
|
|
"cladius": "127.0.0.1:9101",
|
|
|
|
|
"local": "127.0.0.1:9101",
|
|
|
|
|
}
|
|
|
|
|
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
if addr, ok := aliases[core.Lower(host)]; ok {
|
2026-03-17 17:45:04 +00:00
|
|
|
return addr
|
|
|
|
|
}
|
|
|
|
|
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
if !core.Contains(host, ":") {
|
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
|
|
|
return core.Concat(host, ":9101")
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return host
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 04:33:36 +00:00
|
|
|
// token := remoteToken("charon")
|
2026-03-17 17:45:04 +00:00
|
|
|
func remoteToken(host string) string {
|
2026-04-02 09:12:49 +00:00
|
|
|
host = core.Trim(host)
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
envKey := core.Sprintf("AGENT_TOKEN_%s", core.Upper(host))
|
refactor(agentic): adopt core.Env() + core.Path() across package
Replace all os.UserHomeDir/os.Getenv/os.Hostname with core.Env().
Replace all filepath.Base/Dir/Glob/IsAbs with core.PathBase/PathDir/
PathGlob/PathIsAbs.
10 files migrated: paths, prep, review_queue, remote, dispatch,
ingest, mirror, plan, verify, watch.
Imports eliminated: 5x os, 7x filepath. All file I/O and path
construction now routes through Core primitives.
Bumps dappco.re/go/core to v0.6.0.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 10:15:15 +00:00
|
|
|
if token := core.Env(envKey); token != "" {
|
2026-03-17 17:45:04 +00:00
|
|
|
return token
|
|
|
|
|
}
|
|
|
|
|
|
refactor(agentic): adopt core.Env() + core.Path() across package
Replace all os.UserHomeDir/os.Getenv/os.Hostname with core.Env().
Replace all filepath.Base/Dir/Glob/IsAbs with core.PathBase/PathDir/
PathGlob/PathIsAbs.
10 files migrated: paths, prep, review_queue, remote, dispatch,
ingest, mirror, plan, verify, watch.
Imports eliminated: 5x os, 7x filepath. All file I/O and path
construction now routes through Core primitives.
Bumps dappco.re/go/core to v0.6.0.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 10:15:15 +00:00
|
|
|
if token := core.Env("MCP_AUTH_TOKEN"); token != "" {
|
2026-03-17 17:45:04 +00:00
|
|
|
return token
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:52:15 +00:00
|
|
|
home := HomeDir()
|
2026-03-17 17:45:04 +00:00
|
|
|
tokenFiles := []string{
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
core.Sprintf("%s/.core/tokens/%s.token", home, core.Lower(host)),
|
|
|
|
|
core.Sprintf("%s/.core/agent-token", home),
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
for _, f := range tokenFiles {
|
2026-03-22 03:41:07 +00:00
|
|
|
if r := fs.Read(f); r.OK {
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
return core.Trim(r.Value.(string))
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
}
|