Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f4590d9
Add pinned Subagent public read interfaces
SaladDay Sep 21, 2026
14495bb
Add phased live Subagent acceptance script
SaladDay Sep 21, 2026
7509a59
Observe Codex subagent history and settle child work before release
SaladDay Sep 21, 2026
e18138b
Bound persisted child identity lookups during settlement
SaladDay Sep 21, 2026
0355053
Reject unresolved native lifecycle effects at settlement
SaladDay Sep 21, 2026
ceddda4
Persist shared subagent resources and wire official reads
SaladDay Sep 21, 2026
6610305
Separate common Subagent read acceptance from native lifecycle fixtures
SaladDay Sep 21, 2026
e7cb3bb
Verify leased Subagent history and lifecycle dispatch
SaladDay Sep 21, 2026
8272f24
Compare child tool replay using persisted JSON semantics
SaladDay Sep 21, 2026
82bc104
Add common two-child live Subagent fixture
SaladDay Sep 21, 2026
5ffe82f
Admit the packaged managed pre-hook for subagent observation
SaladDay Sep 21, 2026
7383646
Clarify the unqualified child tool-environment failure path
SaladDay Sep 21, 2026
50b7fda
Bound fixture observation after native settlement
SaladDay Sep 21, 2026
f63e368
Use native child loading before cold close acceptance
SaladDay Sep 21, 2026
0a97b2f
Qualify Subagent configurations through harness profiles
SaladDay Sep 21, 2026
f4d2d6b
feat(claude): observe native subagent histories and cancellation effects
SaladDay Sep 21, 2026
c8d38d7
Discover qualified Claude Subagent observations
SaladDay Sep 21, 2026
4c58981
fix(claude): reserve idle child identity before continuation starts
SaladDay Sep 21, 2026
4355f40
fix(claude): project child input through neutral message envelope
SaladDay Sep 21, 2026
81856d0
Preserve incomplete native child messages after cancellation
SaladDay Sep 21, 2026
1a33bad
Implement native MiniMax Subagent observations and bounded admission
SaladDay Sep 21, 2026
10cd63b
Wire common Subagent discovery and document native facts
SaladDay Sep 21, 2026
b514325
Close unused MiniMax preparation without child settlement
SaladDay Sep 21, 2026
79c1fa0
Clarify child cancellation facts and protected tool qualification
SaladDay Sep 21, 2026
9dc1566
Record three-harness Subagent workflows and qualification limits
SaladDay Sep 21, 2026
3a735a8
Verify Subagent support in exported Claude runtimes
SaladDay Sep 21, 2026
2886d2e
Reconcile remaining coverage with qualified Subagent reads
SaladDay Sep 21, 2026
8fc5d15
Retain Codex cancellation ownership across caller deadlines
SaladDay Sep 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 48 additions & 32 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ bounded task from the complete board; nonblocking local improvements stay queued
Authentication, tenant/credential isolation, state consistency and data loss remain
material acceptance requirements. Optional feature equality is not required. After
the three profiles pass merged-main validation, publish the results, limitations
and backlog, then stop development until new user direction. Additional harness
implementations and protocol Subagent execution remain queued without changing the
complete pinned protocol target.
and backlog, then stop development until new user direction. Additional harness implementations remain queued. The separately authorized
Subagent batch targets the six read operations across these three harnesses and
does not change the complete pinned protocol target.

The current hosted architecture is V1: Core runs independently; each Environment
sandbox contains its daemon, selected native harness, local tools and workspace.
Expand Down Expand Up @@ -923,8 +923,11 @@ publication permanently transfers resource tracking to the Run; subsequent relea
does not restore preparation ownership or make its old handle cancel that Run.
Forwarded permission and user-choice observations from that cancelled handoff do
not register actionable interactions. Codex prepared cancellation also waits for
the transferred Session's local cleanup, which can finish after output closes;
ordinary Session cancellation retains its existing behavior.
the transferred Session's local cleanup, which can finish after output closes.
Codex cancellation has one continuing native owner: caller deadlines bound only
their wait, leaving child observation and cleanup alive for an explicit retry.
Only observed child terminal facts can settle the child; actual observation
failures remain failures. Native process-exit confirmation remains retryable.
One prepared-output consumer starts before `Prepared.Start`, so native output beyond
the 64-frame channel capacity cannot deadlock Start. It retains the first terminal
frame, drains later output, and forwards accepted frames before the observed cancellation
Expand Down Expand Up @@ -1521,29 +1524,25 @@ replaced; do not carry obsolete compatibility code forward to satisfy this secti
`disable_subagents` policy on both new and resumed Turns. Native translation
stays in the adapter: Codex disables both multi-agent feature generations,
overriding operator feature preferences. Product prompts that omit the policy
retain their defaults. Enabled multi-agent execution and public Subagent
resources remain separate implementation gaps; the Agent tools list is not
retain their defaults. Enabled multi-agent observations require separate operation qualification; the Agent tools list is not
proven to enumerate every harness-internal utility.
- Private `observe_subagent_identities` requests discover direct root children
from completed native spawn/resume Items. The Codex adapter verifies exact child
identity, persisted parent and original spawn-source parent against its RPC-bound
root. Use parent-filtered persisted `thread/list`; `thread/read` can synthesize
creation time before persistence. Native fields stay in the adapter. One worker
allows 64 candidates and 64 metadata RPCs per dispatch, four 100-row pages per
lookup pass, and three seconds per lookup. Root terminal content and Usage freeze
before a separate, three-second settlement wait; keep the reader free for RPC
replies and deliver successful observations before Done. Owner cancellation,
missing persistence, overflow, failed spawn and late discovery remain explicit
gaps, never invented identities or public closure. Child lifetime is unchanged.
The leased execution journal projects neutral identity facts in its existing
Session transaction; unrequested observations are rejected. Device and engine
come from the authorized Session binding, not daemon-supplied project ownership.
The service assigns a stable ID unique within device/engine/native identity and
freezes Session, parent, native creation and first-event provenance. Conflicts
roll back the whole event batch; identical or later continuation observations
preserve the original binding. Internal reads enforce project and visible Session
scope. This is a private consumer prerequisite, not public Subagent admission,
lifecycle, child output reconstruction or complete discovery/recovery.
- Subagent resources use the common observations in
`internal/agentdaemon/proto/subagents.go`: verified identity, successful lifecycle
effects, native-owned Turns/Items and neutral coordination operations. Core
assigns public IDs and projects them under the existing Session lock and leased
execution journal. Native names, history parsing and outcome proof stay in
adapters. Public GETs read persisted resources without starting native work.
Child Turns have a native writer and a separate table from the Core queue;
`public_execution_turns` provides the shared Session read/pagination view.
Session Items stay root-owned; copied parent transcripts never become child work.
Repeated effects are idempotent. Active includes idle; task completion, process
release and cancellation cannot fabricate public closure. Native timestamps
retain their actual precision and unknown Usage stays null.
Reuse the existing native owner for child settlement and cancellation, freeze
root output first, and deliver child Items before their terminal Turn snapshot.
Do not add another scheduler or a broad recovery framework. Capability
advertisements do not qualify unsupported native facts. The exact read contract,
admission limits and remaining evidence are in [Subagents](contracts/agents-api/subagents.md).
- `function_tools` advertises the optional native function-call bridge. Explicit
prompt definitions become Codex dynamic tools; unchanged prompts carry none.
Requests and ordered text/image results are scoped by Run and native call ID.
Expand Down Expand Up @@ -1820,23 +1819,37 @@ for implementation and registration steps.

MiniMax Code's opt-in Agents API profile qualifies native ACP 0.4.12 for
`environment:none` text execution. It reuses the shared lifecycle without public
workspace, functions, MCP or native Subagents. Native configuration disables
workspace, functions or MCP. Enabled Subagents use the separately qualified
common observation path below. Native configuration disables
file/shell authority and external
capability discovery; the child receives a private Session home and a restricted
environment. Active-input application requires a native ACP receipt, cancellation
settles the process and output, and continuation requires the exact owned native
history. Do not infer history IDs or qualify hosted execution from this text
profile. See [deployment and acceptance](services/agents-api/deploy/mcode/README.md).

The MiniMax workspace profile retains the published CLI and isolates native
The MiniMax workspace profile builds one CLI from the fixed upstream source and
lockfile through the existing companion packaging path, and isolates native
workspace tools behind its standard MCP client. The process and native Session
share one private control directory; public workspace files cannot configure that
process or become privileged project instructions. A trusted adapter-owned bridge
runs the original six tool implementations in the upstream Linux sandbox, with no
unsandboxed fallback. Keep native history bound to the control directory and Files/
Artifacts bound to the public workspace. Core and shared file helpers remain engine
neutral. This internal MCP transport does not admit public MCP configuration.
Record published CLI and worker-source provenance separately; complete
Record the upstream revision, native admission patch hashes and worker-source
provenance. The bounded patch checks the shared descendant-task limit inside the
existing native SQLite admission transaction before start, without another
scheduler. ACP initialization must acknowledge the applied limit before input.
The native tool catalog applies the protected workspace policy to every child,
not only the root's configured profile. Only the Session's authorized internal
workspace MCP entry crosses the native child selector; this does not grant
external MCP access or bypass the native profile's read/write restrictions.
Initialization must acknowledge that protected tool policy before input as well.
Subagent reads use the Session-private protected native database. Multi-agent
workspace execution installs only the existing authorized workspace MCP entry in
that private native configuration so children inherit the same tools; public MCP
and Environment-origin MCP combinations remain separately qualified. Complete
[workspace acceptance](contracts/agents-api/mcode-workspace-v1.md) before enabling
hosted execution. The standalone companion uses its own npm lock; `make check`
runs its lifecycle tests and script checks, while its exact-source Linux build and
Expand Down Expand Up @@ -2156,8 +2169,11 @@ declared functions with ordered text results, and the HTTP MCP subset
described above. It rejects unsupported request
options and disables built-in tools and undeclared MCP discovery.
`DisableExecutionEnvironment` and `DisableSubagents` are accepted assertions about
this fixed restrictive profile. Omission does not enable built-in tools. New and
resumed queries use the SDK's empty built-in tool set, explicit function MCP
the single-Agent restrictive profile. Omission does not enable built-in tools.
Explicit Subagent observation enables only its qualified native delegation tools,
with admission before start and verified child identity before workspace authority.
Public function/MCP combinations remain unqualified with Subagents. Single-Agent
new and resumed queries use the SDK's empty built-in tool set, explicit function MCP
configuration and allowlist, strict MCP configuration and empty user/project/local
setting sources. Without HTTP MCP declarations, native initialization and real
provider request inventories must contain only the declared host functions. Managed operator policy may further
Expand Down
20 changes: 18 additions & 2 deletions apps/parsar-daemon/internal/agent/claudesdk/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ type Config struct {
Workspace *WorkspaceConfig
}

type subagentOptions struct {
MaxConcurrent int `json:"max_concurrent"`
}

type startRequest struct {
Subagents *subagentOptions `json:"subagents,omitempty"`
Type string `json:"type"`
Prompt string `json:"prompt,omitempty"`
Model string `json:"model"`
Expand Down Expand Up @@ -61,8 +66,19 @@ func prepareConfiguration(config Config, req proto.PromptRequestPayload) (startR
if controls := req.ExecutionControls; controls != nil && (controls.WebSearch != "disabled" || controls.TextVerbosity != "medium") {
return fail("execution controls require disabled web search and medium text verbosity")
}
// The fixed SDK profile already excludes all built-in tools and subagents.
// Both restriction flags are supported; omitting them does not widen the profile.
if req.ObserveSubagentIdentities {
if req.DisableSubagents || len(req.FunctionTools) != 0 || req.MCPHTTPServers != nil || (req.LocalEnvironment != nil && len(req.LocalEnvironment.MCP) != 0) {
return fail("subagent execution does not support this tool combination")
}
limit := 6
if req.MaxConcurrentSubagents != nil {
limit = *req.MaxConcurrentSubagents
}
if limit < 1 {
return fail("invalid concurrent subagent limit")
}
start.Subagents = &subagentOptions{MaxConcurrent: limit}
}
if err := validateFunctions(req.FunctionTools); err != nil {
return startRequest{}, nil, err
}
Expand Down
7 changes: 5 additions & 2 deletions apps/parsar-daemon/internal/agent/claudesdk/preparation.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func NewPreparationFactory(config Config) agent.PreparationFactory {
if owner == nil {
owner = context.Background()
}
if config.Workspace == nil || req.RunID != "" || req.Prompt != "" || req.ConversationID != "" || req.ObserveSubagentIdentities {
return nil, fmt.Errorf("claudesdk: preparation requires workspace configuration without input, conversation or subagents")
if config.Workspace == nil || req.RunID != "" || req.Prompt != "" || req.ConversationID != "" {
return nil, fmt.Errorf("claudesdk: preparation requires workspace configuration without input or conversation")
}
snapshot := config
snapshot.Env = slices.Clone(config.Env)
Expand All @@ -53,6 +53,9 @@ func NewPreparationFactory(config Config) agent.PreparationFactory {
if err != nil || !info.supportsWorkspacePreparation() {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support workspace preparation")
}
if start.Subagents != nil && !info.SupportsSubagents() {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support subagent resources")
}
if start.observeFunctions && !info.supportsWorkspaceCommands() {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support workspace command observations")
}
Expand Down
4 changes: 4 additions & 0 deletions apps/parsar-daemon/internal/agent/claudesdk/readiness.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ type RuntimeInfo struct {
Features []string `json:"features"`
}

func (info RuntimeInfo) SupportsSubagents() bool {
return slices.Contains(info.Features, "subagent_resources")
}

func (info RuntimeInfo) SupportsHTTPMCP() bool {
return slices.Contains(info.Features, "mcp_http_tools")
}
Expand Down
29 changes: 21 additions & 8 deletions apps/parsar-daemon/internal/agent/claudesdk/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,22 @@ func NewFactory(config Config) agent.Factory {
if err != nil {
return nil, err
}
if start.MCPHTTPServers != nil {
if start.MCPHTTPServers != nil || start.Subagents != nil {
info, err := CheckRuntime(ctx, config)
if err != nil || !info.SupportsHTTPMCP() {
if start.Subagents != nil && (err != nil || !info.SupportsSubagents()) {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support subagent resources")
}
if start.MCPHTTPServers != nil && (err != nil || !info.SupportsHTTPMCP()) {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support HTTP MCP")
}
for _, server := range *start.MCPHTTPServers {
if server.Required && !info.SupportsHTTPMCPRequired() {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support required HTTP MCP")
}
if server.BearerTokenEnvVar != "" && !info.SupportsHTTPMCPBearer() {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support authenticated HTTP MCP")
if start.MCPHTTPServers != nil {
for _, server := range *start.MCPHTTPServers {
if server.Required && !info.SupportsHTTPMCPRequired() {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support required HTTP MCP")
}
if server.BearerTokenEnvVar != "" && !info.SupportsHTTPMCPBearer() {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support authenticated HTTP MCP")
}
}
}
}
Expand All @@ -73,6 +78,7 @@ func NewFactory(config Config) agent.Factory {
}

type bridgeEvent struct {
Fact json.RawMessage `json:"fact"`
InputID string `json:"input_id"`
ResultID string `json:"result_id"`
Usage json.RawMessage `json:"usage,omitempty"`
Expand Down Expand Up @@ -207,6 +213,13 @@ func (s *session) run(ctx context.Context, runID string, start startRequest, out
failure = err
s.process.Cancel()
}
case proto.TypeSubagentIdentity, proto.TypeSubagentTurn, proto.TypeSubagentItem, proto.TypeSubagentCoordination:
if start.Subagents == nil || !json.Valid(event.Fact) {
failure = fmt.Errorf("claudesdk: unrequested native child observation")
s.process.Cancel()
break
}
emit(event.Type, event.Fact)
case "usage":
if event.ResultID == "" || !s.matchesInputSession(event.SessionID) || event.SessionID == "" || (start.Resume != "" && event.SessionID != start.Resume) ||
(usageSession != "" && usageSession != event.SessionID) || usageIDs[event.ResultID] {
Expand Down
48 changes: 48 additions & 0 deletions apps/parsar-daemon/internal/agent/claudesdk/subagents_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package claudesdk

import (
"testing"

"github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto"
)

func TestSubagentConfigurationUsesExplicitRequestAndFrozenLimit(t *testing.T) {
config := workspaceFixture(t)
req := workspaceRequest()
start, _, err := prepare(config, req)
if err != nil || start.Subagents != nil {
t.Fatal("ordinary execution changed", err)
}
req.DisableSubagents, req.ObserveSubagentIdentities = false, true
start, _, err = prepare(config, req)
if err != nil || start.Subagents == nil || start.Subagents.MaxConcurrent != 6 {
t.Fatal("missing default native admission limit", err)
}
limit := 2
req.MaxConcurrentSubagents = &limit
start, _, err = prepare(config, req)
limit = 4
if err != nil || start.Subagents.MaxConcurrent != 2 {
t.Fatal("subagent configuration was not frozen", err)
}
}

func TestSubagentConfigurationRejectsUnqualifiedAuthority(t *testing.T) {
config := workspaceFixture(t)
for _, change := range []func(*proto.PromptRequestPayload){
func(r *proto.PromptRequestPayload) { r.DisableSubagents = true },
func(r *proto.PromptRequestPayload) { n := 0; r.MaxConcurrentSubagents = &n },
func(r *proto.PromptRequestPayload) { r.FunctionTools = []proto.FunctionTool{{Name: "function"}} },
func(r *proto.PromptRequestPayload) { v := []proto.MCPHTTPServer{}; r.MCPHTTPServers = &v },
} {
req := workspaceRequest()
req.DisableSubagents, req.ObserveSubagentIdentities = false, true
change(&req)
if _, _, err := prepare(config, req); err == nil {
t.Fatal("unqualified subagent combination accepted")
}
}
if (RuntimeInfo{}).SupportsSubagents() || !(RuntimeInfo{Features: []string{"subagent_resources"}}).SupportsSubagents() {
t.Fatal("subagent readiness did not require the packaged feature")
}
}
6 changes: 6 additions & 0 deletions apps/parsar-daemon/internal/agent/codex/preparation.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func newPreparation(parent context.Context, req proto.PromptRequestPayload, cfg
rpc := NewJSONRPCClient(rpcCfg)

s := &Session{
nativeHome: nativeHomeFromPlan(plan),
toolEnvironment: req.LocalEnvironment != nil && req.LocalEnvironment.ToolEnvironment,
functions: functions,
observeMessages: req.ObserveMessages,
Expand Down Expand Up @@ -127,6 +128,11 @@ func newPreparation(parent context.Context, req proto.PromptRequestPayload, cfg
return nil, err
}
}
if s.observeSubagentIdentities {
if err := verifySubagentObservationProfile(cancelCtx, rpc, plan.Cwd); err != nil {
return p.preparationFailed(err)
}
}
if s.toolEnvironment {
if err := verifyToolEnvironmentHook(cancelCtx, rpc, plan.Cwd); err != nil {
return p.preparationFailed(err)
Expand Down
12 changes: 9 additions & 3 deletions apps/parsar-daemon/internal/agent/codex/prepared.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,15 @@ func (p *Prepared) Cancel(ctx context.Context) error {
started := p.started
p.mu.Unlock()
if started {
err := p.session.Cancel(ctx)
<-p.session.waitDone
return err
if err := p.session.Cancel(ctx); err != nil {
return err
}
select {
case <-p.session.waitDone:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
return p.Close()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package codex

import (
"context"
"errors"
"reflect"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -169,6 +170,11 @@ func TestPreparedCancelTransferredWaitsForCleanup(t *testing.T) {
t.Fatal("Session finished before local cleanup")
default:
}
ctx, cancel := context.WithTimeout(t.Context(), 20*time.Millisecond)
defer cancel()
if err := p.Cancel(ctx); !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("blocked cleanup ignored caller deadline: %v", err)
}
allowCleanup()
select {
case err := <-finished:
Expand All @@ -179,6 +185,9 @@ func TestPreparedCancelTransferredWaitsForCleanup(t *testing.T) {
t.Fatal("cancellation did not finish after local cleanup")
}
waitPreparedRelease(t, p, root)
if err := p.Cancel(t.Context()); err != nil {
t.Fatalf("settled cleanup could not be retried: %v", err)
}
}

func TestPreparedCancelRacingTransfer(t *testing.T) {
Expand Down
Loading
Loading