From f0c903d8c3de9f60cacae0cbd7e7f9724cd1cc32 Mon Sep 17 00:00:00 2001 From: Virgil Date: Tue, 31 Mar 2026 05:43:14 +0000 Subject: [PATCH] fix(ax): replace typed service lookups with Core.Service Co-Authored-By: Virgil --- cmd/core-agent/main_test.go | 25 +++++++++++++++---------- cmd/core-agent/mcp_service.go | 26 ++++++++++++++------------ pkg/agentic/dispatch.go | 20 ++++++++++++++------ pkg/agentic/handlers.go | 6 ++++-- pkg/agentic/pid.go | 24 ++++++++++++++---------- pkg/agentic/process_register.go | 6 +++--- pkg/agentic/register.go | 2 +- pkg/agentic/register_test.go | 10 +++++++--- pkg/brain/register.go | 4 ++-- pkg/monitor/monitor.go | 16 +++++++++------- pkg/monitor/monitor_example_test.go | 3 ++- pkg/monitor/monitor_test.go | 4 +++- pkg/monitor/register.go | 4 ++-- pkg/monitor/register_example_test.go | 3 ++- pkg/monitor/register_test.go | 16 ++++++++++++---- pkg/runner/queue.go | 9 +++++++-- pkg/runner/runner.go | 22 +++++++++++++++------- pkg/setup/detect.go | 2 +- pkg/setup/service.go | 4 ++-- pkg/setup/service_example_test.go | 5 +++-- pkg/setup/service_test.go | 4 +++- 21 files changed, 135 insertions(+), 80 deletions(-) diff --git a/cmd/core-agent/main_test.go b/cmd/core-agent/main_test.go index 9e400c6..2b3ea1c 100644 --- a/cmd/core-agent/main_test.go +++ b/cmd/core-agent/main_test.go @@ -40,16 +40,21 @@ func TestMain_NewCoreAgent_Good(t *testing.T) { assert.Contains(t, c.Commands(), "env") assert.Contains(t, c.Actions(), "process.run") - _, ok := core.ServiceFor[*agentic.PrepSubsystem](c, "agentic") - assert.True(t, ok) - _, ok = core.ServiceFor[*runner.Service](c, "runner") - assert.True(t, ok) - _, ok = core.ServiceFor[*monitor.Subsystem](c, "monitor") - assert.True(t, ok) - _, ok = core.ServiceFor[*brain.DirectSubsystem](c, "brain") - assert.True(t, ok) - _, ok = core.ServiceFor[*mcp.Service](c, "mcp") - assert.True(t, ok) + service := c.Service("agentic") + assert.True(t, service.OK) + assert.IsType(t, &agentic.PrepSubsystem{}, service.Value) + service = c.Service("runner") + assert.True(t, service.OK) + assert.IsType(t, &runner.Service{}, service.Value) + service = c.Service("monitor") + assert.True(t, service.OK) + assert.IsType(t, &monitor.Subsystem{}, service.Value) + service = c.Service("brain") + assert.True(t, service.OK) + assert.IsType(t, &brain.DirectSubsystem{}, service.Value) + service = c.Service("mcp") + assert.True(t, service.OK) + assert.IsType(t, &mcp.Service{}, service.Value) } func TestMain_NewCoreAgentBanner_Good(t *testing.T) { diff --git a/cmd/core-agent/mcp_service.go b/cmd/core-agent/mcp_service.go index c225875..3ded616 100644 --- a/cmd/core-agent/mcp_service.go +++ b/cmd/core-agent/mcp_service.go @@ -3,15 +3,12 @@ package main import ( - "dappco.re/go/agent/pkg/agentic" - "dappco.re/go/agent/pkg/brain" - "dappco.re/go/agent/pkg/monitor" core "dappco.re/go/core" "forge.lthn.ai/core/mcp/pkg/mcp" ) // c := core.New(core.WithService(registerMCPService)) -// _, ok := core.ServiceFor[*mcp.Service](c, "mcp") +// service := c.Service("mcp") func registerMCPService(c *core.Core) core.Result { if c == nil { return core.Result{Value: core.E("main.registerMCPService", "core is required", nil), OK: false} @@ -19,15 +16,20 @@ func registerMCPService(c *core.Core) core.Result { var registeredSubsystems []mcp.Subsystem - if agenticSubsystem, ok := core.ServiceFor[*agentic.PrepSubsystem](c, "agentic"); ok { - registeredSubsystems = append(registeredSubsystems, agenticSubsystem) - } - if monitorSubsystem, ok := core.ServiceFor[*monitor.Subsystem](c, "monitor"); ok { - registeredSubsystems = append(registeredSubsystems, monitorSubsystem) - } - if brainSubsystem, ok := core.ServiceFor[*brain.DirectSubsystem](c, "brain"); ok { - registeredSubsystems = append(registeredSubsystems, brainSubsystem) + appendSubsystem := func(name string) { + serviceResult := c.Service(name) + if !serviceResult.OK { + return + } + subsystem, ok := serviceResult.Value.(mcp.Subsystem) + if !ok { + return + } + registeredSubsystems = append(registeredSubsystems, subsystem) } + appendSubsystem("agentic") + appendSubsystem("monitor") + appendSubsystem("brain") service, err := mcp.New(mcp.Options{ Subsystems: registeredSubsystems, diff --git a/pkg/agentic/dispatch.go b/pkg/agentic/dispatch.go index ac443d3..0779033 100644 --- a/pkg/agentic/dispatch.go +++ b/pkg/agentic/dispatch.go @@ -355,10 +355,14 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, workspaceDir string) (int, str command, args = containerCommand(command, args, repoDir, metaDir) - procSvc, ok := core.ServiceFor[*process.Service](s.Core(), "process") - if !ok { + processResult := s.Core().Service("process") + if !processResult.OK { return 0, "", "", core.E("dispatch.spawnAgent", "process service not registered", nil) } + procSvc, ok := processResult.Value.(*process.Service) + if !ok { + return 0, "", "", core.E("dispatch.spawnAgent", "process service has unexpected type", nil) + } proc, err := procSvc.StartWithOptions(context.Background(), process.RunOptions{ Command: command, Args: args, @@ -520,8 +524,10 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, callRequest *mcp.CallToolR Runs: 0, } writeStatusResult(workspaceDir, workspaceStatus) - if runnerSvc, ok := core.ServiceFor[workspaceTracker](s.Core(), "runner"); ok { - runnerSvc.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus) + if runnerResult := s.Core().Service("runner"); runnerResult.OK { + if runnerSvc, ok := runnerResult.Value.(workspaceTracker); ok { + runnerSvc.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus) + } } return nil, DispatchOutput{ Success: true, @@ -552,8 +558,10 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, callRequest *mcp.CallToolR } writeStatusResult(workspaceDir, workspaceStatus) if s.ServiceRuntime != nil { - if runnerSvc, ok := core.ServiceFor[workspaceTracker](s.Core(), "runner"); ok { - runnerSvc.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus) + if runnerResult := s.Core().Service("runner"); runnerResult.OK { + if runnerSvc, ok := runnerResult.Value.(workspaceTracker); ok { + runnerSvc.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus) + } } } diff --git a/pkg/agentic/handlers.go b/pkg/agentic/handlers.go index 8f6d3dd..aeee5b7 100644 --- a/pkg/agentic/handlers.go +++ b/pkg/agentic/handlers.go @@ -36,8 +36,10 @@ func (s *PrepSubsystem) HandleIPCEvents(c *core.Core, msg core.Message) core.Res workspaceStatus.PID = pid workspaceStatus.ProcessID = processID writeStatusResult(workspaceDir, workspaceStatus) - if runnerSvc, ok := core.ServiceFor[workspaceTracker](c, "runner"); ok { - runnerSvc.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus) + if runnerResult := c.Service("runner"); runnerResult.OK { + if runnerSvc, ok := runnerResult.Value.(workspaceTracker); ok { + runnerSvc.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus) + } } } _ = outputFile diff --git a/pkg/agentic/pid.go b/pkg/agentic/pid.go index 8b14fa8..913e4e4 100644 --- a/pkg/agentic/pid.go +++ b/pkg/agentic/pid.go @@ -10,11 +10,7 @@ import ( // alive := agentic.ProcessAlive(c, proc.ID, proc.Info().PID) // alive := agentic.ProcessAlive(c, "", 12345) // legacy PID fallback func ProcessAlive(c *core.Core, processID string, pid int) bool { - if c == nil { - return false - } - - service, ok := core.ServiceFor[*process.Service](c, "process") + service, ok := lookupProcessService(c) if !ok { return false } @@ -40,11 +36,7 @@ func ProcessAlive(c *core.Core, processID string, pid int) bool { // terminated := agentic.ProcessTerminate(c, proc.ID, proc.Info().PID) func ProcessTerminate(c *core.Core, processID string, pid int) bool { - if c == nil { - return false - } - - service, ok := core.ServiceFor[*process.Service](c, "process") + service, ok := lookupProcessService(c) if !ok { return false } @@ -67,3 +59,15 @@ func ProcessTerminate(c *core.Core, processID string, pid int) bool { return false } + +func lookupProcessService(c *core.Core) (*process.Service, bool) { + if c == nil { + return nil, false + } + result := c.Service("process") + if !result.OK { + return nil, false + } + service, ok := result.Value.(*process.Service) + return service, ok +} diff --git a/pkg/agentic/process_register.go b/pkg/agentic/process_register.go index 3f4f4e2..c2910cf 100644 --- a/pkg/agentic/process_register.go +++ b/pkg/agentic/process_register.go @@ -13,13 +13,13 @@ type processActionHandlers struct { service *process.Service } -// c := core.New(core.WithService(agentic.ProcessRegister)) -// processService, _ := core.ServiceFor[*process.Service](c, "process") +// c := core.New(core.WithService(agentic.ProcessRegister)) +// processService := c.Service("process") func ProcessRegister(c *core.Core) core.Result { if c == nil { return core.Result{Value: core.E("agentic.ProcessRegister", "core is required", nil), OK: false} } - if _, ok := core.ServiceFor[*process.Service](c, "process"); ok { + if result := c.Service("process"); result.OK { return core.Result{OK: true} } diff --git a/pkg/agentic/register.go b/pkg/agentic/register.go index c75c71d..051762c 100644 --- a/pkg/agentic/register.go +++ b/pkg/agentic/register.go @@ -12,7 +12,7 @@ import ( // core.WithService(agentic.Register), // // ) -// prep, _ := core.ServiceFor[*agentic.PrepSubsystem](c, "agentic") +// prep := c.Service("agentic") func Register(c *core.Core) core.Result { subsystem := NewPrep() subsystem.ServiceRuntime = core.NewServiceRuntime(c, AgentOptions{}) diff --git a/pkg/agentic/register_test.go b/pkg/agentic/register_test.go index 2de2d76..5ef3406 100644 --- a/pkg/agentic/register_test.go +++ b/pkg/agentic/register_test.go @@ -23,8 +23,10 @@ func TestRegister_ServiceRegistered_Good(t *testing.T) { require.NotNil(t, c) // Service auto-registered under the last segment of the package path: "agentic" - prep, ok := core.ServiceFor[*PrepSubsystem](c, "agentic") - assert.True(t, ok, "PrepSubsystem must be registered as \"agentic\"") + service := c.Service("agentic") + require.True(t, service.OK) + prep, ok := service.Value.(*PrepSubsystem) + require.True(t, ok, "PrepSubsystem must be registered as \"agentic\"") assert.NotNil(t, prep) } @@ -35,7 +37,9 @@ func TestRegister_CoreWired_Good(t *testing.T) { c := core.New(core.WithService(Register)) - prep, ok := core.ServiceFor[*PrepSubsystem](c, "agentic") + service := c.Service("agentic") + require.True(t, service.OK) + prep, ok := service.Value.(*PrepSubsystem) require.True(t, ok) // Register must wire ServiceRuntime — service needs it for Core access assert.NotNil(t, prep.ServiceRuntime, "Register must set ServiceRuntime") diff --git a/pkg/brain/register.go b/pkg/brain/register.go index 770a18b..5d8fee8 100644 --- a/pkg/brain/register.go +++ b/pkg/brain/register.go @@ -7,8 +7,8 @@ import ( ) // c := core.New(core.WithService(brain.Register)) -// subsystem, _ := core.ServiceFor[*brain.DirectSubsystem](c, "brain") -// core.Println(subsystem.Name()) // "brain" +// subsystem := c.Service("brain") +// core.Println(subsystem.OK) // true func Register(c *core.Core) core.Result { subsystem := NewDirect() return core.Result{Value: subsystem, OK: true} diff --git a/pkg/monitor/monitor.go b/pkg/monitor/monitor.go index a5af4e9..dcf44d6 100644 --- a/pkg/monitor/monitor.go +++ b/pkg/monitor/monitor.go @@ -453,13 +453,15 @@ func (m *Subsystem) checkInbox() string { } if m.ServiceRuntime != nil { - if notifier, ok := core.ServiceFor[channelSender](m.Core(), "mcp"); ok { - for _, inboxMessage := range inboxMessages { - notifier.ChannelSend(context.Background(), "inbox.message", map[string]any{ - "from": inboxMessage.From, - "subject": inboxMessage.Subject, - "content": inboxMessage.Content, - }) + if notifierResult := m.Core().Service("mcp"); notifierResult.OK { + if notifier, ok := notifierResult.Value.(channelSender); ok { + for _, inboxMessage := range inboxMessages { + notifier.ChannelSend(context.Background(), "inbox.message", map[string]any{ + "from": inboxMessage.From, + "subject": inboxMessage.Subject, + "content": inboxMessage.Content, + }) + } } } } diff --git a/pkg/monitor/monitor_example_test.go b/pkg/monitor/monitor_example_test.go index 33550a1..64b5e75 100644 --- a/pkg/monitor/monitor_example_test.go +++ b/pkg/monitor/monitor_example_test.go @@ -17,7 +17,8 @@ func ExampleNew() { func ExampleRegister() { c := core.New(core.WithService(Register)) - svc, ok := core.ServiceFor[*Subsystem](c, "monitor") + service := c.Service("monitor") + svc, ok := service.Value.(*Subsystem) core.Println(ok) core.Println(svc.Name()) // Output: diff --git a/pkg/monitor/monitor_test.go b/pkg/monitor/monitor_test.go index 028877d..fbfe063 100644 --- a/pkg/monitor/monitor_test.go +++ b/pkg/monitor/monitor_test.go @@ -218,7 +218,9 @@ func TestMonitor_HandleAgentCompleted_Good_WithCore(t *testing.T) { fs.EnsureDir(core.JoinPath(wsRoot, "workspace")) c := core.New(core.WithService(Register)) - mon, ok := core.ServiceFor[*Subsystem](c, "monitor") + service := c.Service("monitor") + require.True(t, service.OK) + mon, ok := service.Value.(*Subsystem) require.True(t, ok) c.ACTION(messages.AgentCompleted{Agent: "codex", Repo: "go-io", Workspace: "ws-2", Status: "completed"}) diff --git a/pkg/monitor/register.go b/pkg/monitor/register.go index 9ca3b1e..08c6c75 100644 --- a/pkg/monitor/register.go +++ b/pkg/monitor/register.go @@ -7,8 +7,8 @@ import ( ) // c := core.New(core.WithService(monitor.Register)) -// service, _ := core.ServiceFor[*monitor.Subsystem](c, "monitor") -// core.Println(service.Name()) // "monitor" +// service := c.Service("monitor") +// core.Println(service.OK) // true func Register(c *core.Core) core.Result { service := New(Options{}) service.ServiceRuntime = core.NewServiceRuntime(c, Options{}) diff --git a/pkg/monitor/register_example_test.go b/pkg/monitor/register_example_test.go index 4a006e3..197c12c 100644 --- a/pkg/monitor/register_example_test.go +++ b/pkg/monitor/register_example_test.go @@ -6,7 +6,8 @@ import core "dappco.re/go/core" func ExampleRegister_ipc() { c := core.New(core.WithService(Register)) - svc, ok := core.ServiceFor[*Subsystem](c, "monitor") + service := c.Service("monitor") + svc, ok := service.Value.(*Subsystem) core.Println(ok) core.Println(svc.Name()) // Output: diff --git a/pkg/monitor/register_test.go b/pkg/monitor/register_test.go index e9e50e9..3a988c6 100644 --- a/pkg/monitor/register_test.go +++ b/pkg/monitor/register_test.go @@ -16,7 +16,9 @@ func TestRegister_Register_Good_ReturnsSubsystem(t *testing.T) { t.Setenv("CORE_WORKSPACE", wsRoot) c := core.New(core.WithService(Register)) - svc, ok := core.ServiceFor[*Subsystem](c, "monitor") + service := c.Service("monitor") + assert.True(t, service.OK) + svc, ok := service.Value.(*Subsystem) assert.True(t, ok) assert.NotNil(t, svc) } @@ -34,7 +36,9 @@ func TestRegister_Register_Good_WiresServiceRuntime(t *testing.T) { t.Setenv("CORE_WORKSPACE", wsRoot) c := core.New(core.WithService(Register)) - svc, _ := core.ServiceFor[*Subsystem](c, "monitor") + service := c.Service("monitor") + require.True(t, service.OK) + svc, _ := service.Value.(*Subsystem) assert.NotNil(t, svc.ServiceRuntime) assert.Equal(t, c, svc.Core()) } @@ -45,7 +49,9 @@ func TestRegister_Register_Good_TracksStartedIPC(t *testing.T) { fs.EnsureDir(core.JoinPath(wsRoot, "workspace")) c := core.New(core.WithService(Register)) - svc, ok := core.ServiceFor[*Subsystem](c, "monitor") + service := c.Service("monitor") + require.True(t, service.OK) + svc, ok := service.Value.(*Subsystem) require.True(t, ok) c.ACTION(messages.AgentStarted{Agent: "codex", Repo: "go-io", Workspace: "ws-reg"}) @@ -61,7 +67,9 @@ func TestRegister_Register_Good_TracksCompletedIPC(t *testing.T) { fs.EnsureDir(core.JoinPath(wsRoot, "workspace")) c := core.New(core.WithService(Register)) - svc, ok := core.ServiceFor[*Subsystem](c, "monitor") + service := c.Service("monitor") + require.True(t, service.OK) + svc, ok := service.Value.(*Subsystem) require.True(t, ok) c.ACTION(messages.AgentCompleted{Agent: "codex", Repo: "go-io", Workspace: "ws-done", Status: "completed"}) diff --git a/pkg/runner/queue.go b/pkg/runner/queue.go index 38e99ad..3137125 100644 --- a/pkg/runner/queue.go +++ b/pkg/runner/queue.go @@ -237,11 +237,16 @@ func (s *Service) drainOne() bool { type spawner interface { SpawnFromQueue(agent, prompt, workspaceDir string) core.Result } - agenticService, ok := core.ServiceFor[spawner](s.Core(), "agentic") - if !ok { + agenticResult := s.Core().Service("agentic") + if !agenticResult.OK { core.Error("drainOne: agentic service not found") continue } + agenticService, ok := agenticResult.Value.(spawner) + if !ok { + core.Error("drainOne: agentic service has unexpected type") + continue + } prompt := core.Concat("TASK: ", workspaceStatus.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.") spawnResult := agenticService.SpawnFromQueue(workspaceStatus.Agent, prompt, workspaceDir) if !spawnResult.OK { diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index ee7b570..cebc9dc 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -45,7 +45,7 @@ func New() *Service { } // c := core.New(core.WithService(runner.Register)) -// service, _ := core.ServiceFor[*runner.Service](c, "runner") +// service := c.Service("runner") func Register(coreApp *core.Core) core.Result { service := New() service.ServiceRuntime = core.NewServiceRuntime(coreApp, Options{}) @@ -106,6 +106,18 @@ func (s *Service) OnShutdown(_ context.Context) core.Result { // Agent: "codex", Repo: "go-io", Workspace: "core/go-io/task-5", Status: "completed", // }) func (s *Service) HandleIPCEvents(coreApp *core.Core, msg core.Message) core.Result { + sendNotification := func(channel string, data any) { + serviceResult := coreApp.Service("mcp") + if !serviceResult.OK { + return + } + notifier, ok := serviceResult.Value.(channelSender) + if !ok { + return + } + notifier.ChannelSend(context.Background(), channel, data) + } + switch ev := msg.(type) { case messages.AgentStarted: baseAgentName := baseAgent(ev.Agent) @@ -127,9 +139,7 @@ func (s *Service) HandleIPCEvents(coreApp *core.Core, msg core.Message) core.Res Running: runningCount, Limit: limit, } - if notifier, ok := core.ServiceFor[channelSender](coreApp, "mcp"); ok { - notifier.ChannelSend(context.Background(), "agent.status", notification) - } + sendNotification("agent.status", notification) case messages.AgentCompleted: if ev.Workspace != "" { @@ -166,9 +176,7 @@ func (s *Service) HandleIPCEvents(coreApp *core.Core, msg core.Message) core.Res Running: runningCount, Limit: limit, } - if notifier, ok := core.ServiceFor[channelSender](coreApp, "mcp"); ok { - notifier.ChannelSend(context.Background(), "agent.status", notification) - } + sendNotification("agent.status", notification) s.Poke() case messages.PokeQueue: diff --git a/pkg/setup/detect.go b/pkg/setup/detect.go index ac11d19..2afd6d0 100644 --- a/pkg/setup/detect.go +++ b/pkg/setup/detect.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: EUPL-1.2 -// service := core.ServiceFor[*setup.Service](core.New(core.WithService(setup.Register)), "setup") +// service := core.New(core.WithService(setup.Register)).Service("setup") package setup import ( diff --git a/pkg/setup/service.go b/pkg/setup/service.go index 0f4d194..5352162 100644 --- a/pkg/setup/service.go +++ b/pkg/setup/service.go @@ -12,13 +12,13 @@ import ( type RuntimeOptions struct{} // c := core.New(core.WithService(setup.Register)) -// service, _ := core.ServiceFor[*setup.Service](c, "setup") +// service := c.Service("setup") type Service struct { *core.ServiceRuntime[RuntimeOptions] } // c := core.New(core.WithService(setup.Register)) -// service, _ := core.ServiceFor[*setup.Service](c, "setup") +// service := c.Service("setup") func Register(c *core.Core) core.Result { service := &Service{ ServiceRuntime: core.NewServiceRuntime(c, RuntimeOptions{}), diff --git a/pkg/setup/service_example_test.go b/pkg/setup/service_example_test.go index e729a18..7e2e817 100644 --- a/pkg/setup/service_example_test.go +++ b/pkg/setup/service_example_test.go @@ -6,9 +6,10 @@ import ( core "dappco.re/go/core" ) -func ExampleRegister_serviceFor() { +func ExampleRegister_service() { c := core.New(core.WithService(Register)) - service, ok := core.ServiceFor[*Service](c, "setup") + serviceResult := c.Service("setup") + service, ok := serviceResult.Value.(*Service) core.Println(ok) core.Println(service != nil) // Output: diff --git a/pkg/setup/service_test.go b/pkg/setup/service_test.go index 8016ea8..a67ea80 100644 --- a/pkg/setup/service_test.go +++ b/pkg/setup/service_test.go @@ -14,7 +14,9 @@ import ( func TestService_Register_Good(t *testing.T) { c := core.New(core.WithService(Register)) - svc, ok := core.ServiceFor[*Service](c, "setup") + service := c.Service("setup") + assert.True(t, service.OK) + svc, ok := service.Value.(*Service) assert.True(t, ok) assert.NotNil(t, svc) }