From ebec187862a7ef79e969656b955cb2ba3539ff26 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Tue, 6 Oct 2026 17:46:40 +0000 Subject: [PATCH 1/2] Run MiniMax Code Subagents in agent-host views The view runs the Subagent history reader beside the CLI through ViewSession.Spawn, with node, the reader script and the data directory at their view paths, and no longer rejects Subagents. readSubagents keeps one parsing and limit path for both modes; the host reader check moves to host preparation. --- apps/daemon/internal/agent/mcode/execution.go | 9 ++---- apps/daemon/internal/agent/mcode/options.go | 11 +++++++ apps/daemon/internal/agent/mcode/subagents.go | 27 +++++++++-------- apps/daemon/internal/agent/mcode/view.go | 13 ++++---- apps/daemon/internal/agent/mcode/view_test.go | 30 +++++++++++++++---- 5 files changed, 58 insertions(+), 32 deletions(-) diff --git a/apps/daemon/internal/agent/mcode/execution.go b/apps/daemon/internal/agent/mcode/execution.go index a54bfc742..14282fce4 100644 --- a/apps/daemon/internal/agent/mcode/execution.go +++ b/apps/daemon/internal/agent/mcode/execution.go @@ -19,13 +19,8 @@ func validateExecutionRequest(req proto.PromptRequestPayload) error { if req.ExecutionControls == nil || req.ExecutionControls.OutputFormat != nil || req.ExecutionControls.WebSearch != "disabled" || (req.ExecutionControls.TextVerbosity != "" && req.ExecutionControls.TextVerbosity != "medium") { return fmt.Errorf("mcode: unsupported execution controls") } - if !req.DisableSubagents { - if req.MaxConcurrentSubagents == nil || *req.MaxConcurrentSubagents < 1 { - return fmt.Errorf("mcode: Subagent concurrency limit is required") - } - if _, _, err := subagentReader(); err != nil { - return err - } + if !req.DisableSubagents && (req.MaxConcurrentSubagents == nil || *req.MaxConcurrentSubagents < 1) { + return fmt.Errorf("mcode: Subagent concurrency limit is required") } if mode := optionString(req.AgentOptions, "mode"); mode != "" { return fmt.Errorf("mcode: text execution uses default native permissions") diff --git a/apps/daemon/internal/agent/mcode/options.go b/apps/daemon/internal/agent/mcode/options.go index 4f2975062..5da808d31 100644 --- a/apps/daemon/internal/agent/mcode/options.go +++ b/apps/daemon/internal/agent/mcode/options.go @@ -29,6 +29,12 @@ type launchOptions struct { // the CLI entry when the binary is node rather than the CLI itself. start func(clirunner.StartOptions) (*clirunner.Process, error) script string + // spawn runs the Subagent history reader beside the native process in an + // agent-host view, with reader's node, script and data directory as the + // view presents them. nil runs the installed reader on the host over + // DataDir. + spawn func(clirunner.StartOptions) (*clirunner.Process, error) + reader clirunner.StartOptions // home is an agent-host view's Session home on the host. It contains // DataDir and belongs to the Session user, so the daemon reads DataDir // only within it. @@ -44,6 +50,11 @@ func prepareOptionsWithSkills(ctx context.Context, req proto.PromptRequestPayloa if err := validateOptions(req); err != nil { return result, err } + if req.StrictResume && !req.DisableSubagents { + if _, _, err := subagentReader(); err != nil { + return result, err + } + } root, err := agent.ManagedSkillsRoot("mcode", req.AgentStateKey, req.ConversationID, req.RunID) if err != nil { return result, err diff --git a/apps/daemon/internal/agent/mcode/subagents.go b/apps/daemon/internal/agent/mcode/subagents.go index 65c05a7b2..ccdecf113 100644 --- a/apps/daemon/internal/agent/mcode/subagents.go +++ b/apps/daemon/internal/agent/mcode/subagents.go @@ -6,10 +6,11 @@ import ( "fmt" "io" "os" - "os/exec" "path/filepath" + "slices" "time" + "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent/clirunner" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) @@ -66,26 +67,26 @@ func subagentReader() (string, string, error) { func (s *Session) readSubagents(ctx context.Context) (nativeSubagentSnapshot, error) { var snapshot nativeSubagentSnapshot - node, reader, err := subagentReader() - if err != nil { - return snapshot, err + reader, start := s.opts.reader, s.opts.spawn + if start == nil { + node, script, err := subagentReader() + if err != nil { + return snapshot, err + } + reader, start = clirunner.StartOptions{Binary: node, Args: []string{script, s.opts.DataDir}, Env: executionEnvironment()}, clirunner.Start } ctx, cancel := context.WithTimeout(ctx, 15*time.Second) defer cancel() - command := exec.CommandContext(ctx, node, "--disable-warning=ExperimentalWarning", reader, s.opts.DataDir, s.sessionID) - command.Env = executionEnvironment() - stdout, err := command.StdoutPipe() + reader.Parent, reader.Args = ctx, slices.Concat([]string{"--disable-warning=ExperimentalWarning"}, reader.Args, []string{s.sessionID}) + process, err := start(reader) if err != nil { return snapshot, fmt.Errorf("mcode: child history reader is unavailable") } - if err := command.Start(); err != nil { - return snapshot, fmt.Errorf("mcode: child history reader is unavailable") - } - raw, readErr := io.ReadAll(io.LimitReader(stdout, 64*1024*1024+1)) + raw, readErr := io.ReadAll(io.LimitReader(process.Stdout, 64*1024*1024+1)) if readErr != nil || len(raw) > 64*1024*1024 { - _ = command.Process.Kill() + process.Cancel() } - err = command.Wait() + err = process.Wait() if err != nil || readErr != nil || len(raw) > 64*1024*1024 { return snapshot, fmt.Errorf("mcode: complete child history is unavailable") } diff --git a/apps/daemon/internal/agent/mcode/view.go b/apps/daemon/internal/agent/mcode/view.go index 0651cc89d..8e3c703ed 100644 --- a/apps/daemon/internal/agent/mcode/view.go +++ b/apps/daemon/internal/agent/mcode/view.go @@ -10,13 +10,15 @@ import ( "slices" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" + "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent/clirunner" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent/viewloader" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) // In an agent-host view, node runs the CLI from the closure and the native -// data directory lives in the Session home. The workspace worker runs beside -// the CLI in the same view; bash, rg and git run in the sandbox. +// data directory lives in the Session home. The workspace worker and the +// Subagent history reader run beside the CLI in the same view; bash, rg and +// git run in the sandbox. // Closure names and Session home directories. const ( @@ -154,11 +156,6 @@ func (i viewInstall) prepare(_ context.Context, req proto.PromptRequestPayload, if len(local.Skills) != 0 { return launchOptions{}, fmt.Errorf("%w: a MiniMax Code view does not install Capabilities", agent.ErrUnsupportedOperation) } - // Subagent settlement reads native history with a second process while - // the CLI runs, and a Session has one live view, which runs only the CLI. - if !req.DisableSubagents { - return launchOptions{}, fmt.Errorf("%w: a MiniMax Code view does not run Subagents", agent.ErrUnsupportedOperation) - } workspace := local.WorkspaceRoot if !path.IsAbs(workspace) || path.Clean(workspace) != workspace || workspace == "/" { return launchOptions{}, errors.New("mcode: the workspace is not a canonical absolute path") @@ -209,6 +206,8 @@ func (i viewInstall) prepare(_ context.Context, req proto.PromptRequestPayload, opts.Env = append(opts.Env, "LD_LIBRARY_PATH="+i.loader.LibraryPath) } opts.Env = append(opts.Env, nativeEnvironment(private, dataDir)...) + opts.spawn = session.Spawn + opts.reader = clirunner.StartOptions{Binary: i.node, Args: []string{path.Join(path.Dir(i.bridge), "subagent-snapshot.mjs"), dataDir}, Dir: dataDir, Env: opts.Env, OwnProcessGroup: true} profile := map[string]any{"workspace": workspace, "scratch": tempDir, "network": "enabled"} tools := workspaceTools{node: i.node, bridge: i.bridge, profile: path.Join(dataDir, "workspace-profile.json")} return opts, writeWorkspaceTools(&opts, data, req, tools, profile, nil, servers) diff --git a/apps/daemon/internal/agent/mcode/view_test.go b/apps/daemon/internal/agent/mcode/view_test.go index 5b20c1882..e0d0965ca 100644 --- a/apps/daemon/internal/agent/mcode/view_test.go +++ b/apps/daemon/internal/agent/mcode/view_test.go @@ -75,11 +75,6 @@ func TestViewLaunchesNodeWithGatewayOnly(t *testing.T) { var launched clirunner.StartOptions session := viewSession(&launched) session.Home = agent.ViewDir{Host: t.TempDir(), View: path.Join(agent.ViewPrivateRoot, agent.ViewHomeName)} - subagents := req - subagents.DisableSubagents, subagents.MaxConcurrentSubagents = false, new(2) - if _, err := view.Executor(t.Context(), subagents, session); !errors.Is(err, agent.ErrUnsupportedOperation) || launched.Binary != "" { - t.Fatalf("Executor with Subagents = %v, launched %q", err, launched.Binary) - } if _, err := view.Executor(t.Context(), req, session); err == nil || err.Error() != "launch recorded" { t.Fatalf("Executor = %v", err) } @@ -124,6 +119,31 @@ func TestViewLaunchesNodeWithGatewayOnly(t *testing.T) { } } +func TestViewReadsSubagentsBesideTheCLI(t *testing.T) { + install, _, req := viewFixture(t) + req.DisableSubagents, req.MaxConcurrentSubagents = false, new(2) + var launched, spawned clirunner.StartOptions + session := viewSession(&launched) + session.Home = agent.ViewDir{Host: t.TempDir(), View: path.Join(agent.ViewPrivateRoot, agent.ViewHomeName)} + session.Spawn = func(options clirunner.StartOptions) (*clirunner.Process, error) { + spawned = options + return clirunner.Start(clirunner.StartOptions{Parent: options.Parent, Binary: "/bin/echo", Args: []string{`{"version":1,"complete":true,"rootSessionId":"root"}`}}) + } + opts, err := install.prepare(t.Context(), req, session) + if err != nil { + t.Fatal(err) + } + s := &Session{sessionID: "root", opts: opts} + if _, err := s.readSubagents(t.Context()); err != nil { + t.Fatal(err) + } + data := path.Join(session.Home.View, viewDataName) + args := []string{"--disable-warning=ExperimentalWarning", path.Join(path.Dir(install.bridge), "subagent-snapshot.mjs"), data, "root"} + if spawned.Binary != install.node || !slices.Equal(spawned.Args, args) || spawned.Dir != data || !spawned.OwnProcessGroup || !slices.Contains(spawned.Env, "LD_LIBRARY_PATH="+install.loader.LibraryPath) { + t.Fatalf("spawned %+v", spawned) + } +} + func TestViewDoesNotFollowHomeLinks(t *testing.T) { _, view, req := viewFixture(t) outside := filepath.Join(t.TempDir(), "outside") From c0bae2be81878b88bd76d6685d32caff05b2c4ff Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Tue, 6 Oct 2026 18:53:05 +0000 Subject: [PATCH 2/2] Drain the Subagent reader's stderr --- apps/daemon/internal/agent/mcode/subagents.go | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/daemon/internal/agent/mcode/subagents.go b/apps/daemon/internal/agent/mcode/subagents.go index ccdecf113..9905cbf5b 100644 --- a/apps/daemon/internal/agent/mcode/subagents.go +++ b/apps/daemon/internal/agent/mcode/subagents.go @@ -82,6 +82,7 @@ func (s *Session) readSubagents(ctx context.Context) (nativeSubagentSnapshot, er if err != nil { return snapshot, fmt.Errorf("mcode: child history reader is unavailable") } + go io.Copy(io.Discard, process.Stderr) raw, readErr := io.ReadAll(io.LimitReader(process.Stdout, 64*1024*1024+1)) if readErr != nil || len(raw) > 64*1024*1024 { process.Cancel()