Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 14 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -1999,12 +1999,20 @@ admission requires the selected profile's structured-output qualification, and
only requests using this option require the Runtime's `structured_output` and
message-observation capabilities. A capability advertisement does not qualify a
new public combination. Claude advertises this operation only when the installed
SDK bridge reports its `structured_output` feature and the selected Runtime is
not a workspace profile.

The current qualified path is Claude SDK, `environment:none`, medium verbosity,
single Agent, with optional ordinary function tools and text results. Workspace,
HTTP MCP, Subagent combinations and non-object root schemas remain unqualified.
SDK bridge reports its `structured_output` feature. A workspace Runtime also
requires the complete local Runtime contract and `workspace_structured_output`;
preparation checks that bundle before native launch. These remain adapter readiness
features, not new Core lifecycle or public protocol variants.

The current qualified path is Claude SDK, `environment:none` or Core-managed
Docker `openai_hosted`, medium verbosity, single Agent, with optional ordinary
function tools and text results. The workspace uses its existing preparation and
native sandbox with only the SDK's configured `StructuredOutput` tool added to
inventory and permission checks. Frozen schemas reach preparation before the
input handoff; Start cannot replace them. Skills, Plugins, capability directories,
HTTP MCP, Subagent/tool-discovery combinations and non-object root schemas remain
unqualified. Check resolved template contents as well as inline configuration;
ordinary text requests retain their existing qualifications.
The SDK uses binary64 JSON numbers: reject execution schemas whose numeric values
would change during that conversion, without narrowing saved Agent storage.
Codex and MiniMax structured output remain explicit execution gaps.
Expand Down
2 changes: 1 addition & 1 deletion apps/parsar-daemon/internal/agent/claudesdk/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func prepareConfiguration(config Config, req proto.PromptRequestPayload) (startR
}
if req.ExecutionControls != nil && req.ExecutionControls.OutputFormat != nil {
format := req.ExecutionControls.OutputFormat
if format.Type != "json_schema" || !req.ObserveMessages || !req.DisableSubagents || config.Workspace != nil || req.MCPHTTPServers != nil {
if format.Type != "json_schema" || !req.ObserveMessages || !req.DisableSubagents || req.MCPHTTPServers != nil || (req.LocalEnvironment != nil && (len(req.LocalEnvironment.MCP) != 0 || len(req.LocalEnvironment.Skills) != 0)) {
return fail("structured output requires the qualified message-observing single-agent function profile")
}
if err := proto.ValidateBinary64Schema(format.Schema); err != nil {
Expand Down
3 changes: 3 additions & 0 deletions apps/parsar-daemon/internal/agent/claudesdk/preparation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.OutputFormat != nil && !info.SupportsWorkspaceStructuredOutput() {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support workspace structured output")
}
if start.Subagents != nil && !info.SupportsSubagents() {
return nil, fmt.Errorf("claudesdk: packaged runtime does not support subagent resources")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ func runPreparationHelper() {
} else if mode == "old-command-runtime" {
features = []string{"workspace_tools", "workspace_prepare"}
}
if strings.HasPrefix(mode, "structured-") {
features = append(features, "local_runtime_v1", "structured_output")
if mode == "structured-ready" {
features = append(features, "workspace_structured_output")
}
}
_ = json.NewEncoder(os.Stdout).Encode(RuntimeInfo{Type: "runtime_ready", Protocol: 2, Node: "fixture", SDK: "fixture", MCP: "fixture", Native: "fixture", Features: features})
return
}
Expand Down Expand Up @@ -105,7 +111,12 @@ func runPreparationHelper() {
return
}
emit(bridgeEvent{Type: "input_ready", SessionID: request.Resume})
emit(bridgeEvent{Type: "delta", Delta: "partial"})
if request.ObserveMessages {
text := "completed"
emit(bridgeEvent{Type: "output_message", Message: &proto.OutputMessagePayload{ID: "native-message", Status: "completed", Text: &text}})
} else {
emit(bridgeEvent{Type: "delta", Delta: "partial"})
}
emit(bridgeEvent{Type: "usage", ResultID: "native-result", SessionID: request.Resume, Usage: json.RawMessage(usageFixture)})
emit(bridgeEvent{Type: "input_closed", SessionID: request.Resume})
emit(bridgeEvent{Type: "result", SessionID: request.Resume, Text: "completed"})
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 @@ -41,6 +41,10 @@ func (info RuntimeInfo) SupportsStructuredOutput() bool {
return slices.Contains(info.Features, "structured_output")
}

func (info RuntimeInfo) SupportsWorkspaceStructuredOutput() bool {
return info.SupportsStructuredOutput() && info.SupportsLocalRuntime() && slices.Contains(info.Features, "workspace_structured_output")
}

func (info RuntimeInfo) SupportsSubagents() bool {
return slices.Contains(info.Features, "subagent_resources")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//go:build unix

package claudesdk

import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"

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

func TestWorkspaceStructuredPreparationQualificationAndFrozenSchema(t *testing.T) {
for _, mode := range []string{"structured-missing", "structured-ready"} {
t.Run(mode, func(t *testing.T) {
config := preparationFixture(t, mode)
req := preparationRequest()
req.ObserveMessages = true
schema := `{"type":"object","properties":{"n":{"const":9007199254740992}}}`
req.ExecutionControls = &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium", OutputFormat: &proto.OutputFormat{Type: "json_schema", Schema: json.RawMessage(schema)}}
p, err := NewPreparationFactory(config)(t.Context(), req)
if mode == "structured-missing" {
if err == nil || !strings.Contains(err.Error(), "workspace structured output") {
t.Fatal("unqualified bundle admitted", err)
}
if _, err := os.Stat(filepath.Join(config.StateDir, "launched")); !os.IsNotExist(err) {
t.Fatal("unqualified request reached native launch", err)
}
return
}
if err != nil {
t.Fatal(err)
}
defer p.Close()
req.ExecutionControls.OutputFormat.Schema[0] = ' '
var frozen startRequest
if err := json.Unmarshal(waitPreparationFile(t, filepath.Join(config.StateDir, "prepare.json")), &frozen); err != nil {
t.Fatal(err)
}
if frozen.OutputFormat == nil || string(frozen.OutputFormat.Schema) != schema {
t.Fatal("prepared native schema changed with caller memory")
}
out := make(chan proto.Envelope, 16)
if _, err := p.Start(t.Context(), "run", proto.TextInput("hello"), out); err != nil {
t.Fatal(err)
}
for event := range out {
if event.Type == proto.TypeError {
t.Fatal("prepared execution failed", string(event.Payload))
}
}
var started map[string]json.RawMessage
if err := json.Unmarshal(waitPreparationFile(t, filepath.Join(config.StateDir, "start.json")), &started); err != nil || len(started) != 2 || started["output_format"] != nil {
t.Fatal("Start replaced the prepared configuration", err)
}
})
}
}

func TestWorkspaceStructuredReadinessRequiresCompleteLocalContract(t *testing.T) {
features := []string{"workspace_tools", "workspace_prepare", "workspace_command_observations", "local_runtime_v1", "structured_output", "workspace_structured_output"}
if !(RuntimeInfo{Features: features}).SupportsWorkspaceStructuredOutput() {
t.Fatal("qualified Runtime unavailable")
}
for i, missing := range features {
t.Run(missing, func(t *testing.T) {
partial := append(append([]string{}, features[:i]...), features[i+1:]...)
if (RuntimeInfo{Features: partial}).SupportsWorkspaceStructuredOutput() {
t.Fatal("incomplete workspace bundle admitted")
}
})
}
}
5 changes: 4 additions & 1 deletion apps/parsar-daemon/internal/cli/claude_sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ func discoverClaudeSDK(rc *runContext, profile string, check func(context.Contex
out.Info.Capabilities.MessageImages = info.SupportsMessageImages()
out.Info.Capabilities.FunctionResultImages = info.SupportsFunctionResultImages()
out.Info.Capabilities.ToolSearch = out.Config.Workspace == nil && info.SupportsToolSearch()
out.Info.Capabilities.StructuredOutput = out.Config.Workspace == nil && info.SupportsStructuredOutput()
out.Info.Capabilities.StructuredOutput = info.SupportsStructuredOutput()
if out.Config.Workspace != nil {
out.Info.Capabilities.StructuredOutput = info.SupportsWorkspaceStructuredOutput()
}
out.Info.Capabilities.SubagentObservations = info.SupportsSubagents()
out.Info.Capabilities.MCPHTTPTools = info.SupportsHTTPMCP()
out.Info.Capabilities.MCPHTTPBearerAuth = info.SupportsHTTPMCPBearer()
Expand Down
2 changes: 1 addition & 1 deletion contracts/agents-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ user-managed enrollment remain outside this qualification.
| --- | --- |
| Subagents / multi_agent | Six reads and same-child recovery have three-harness Docker evidence; optional native operations, live child progress, full lifecycle/interactions and tool combinations remain explicit gaps |
| Environment Templates | Unsupported restricted hostname forms, unqualified installation overrides/null network and exact hosted errors remain gaps. CRUD/list, files, env/setup/system/npm/Python, inline/referenced Skills, Plugins, workspace capability directories and Session references have recorded coverage. Environment Plugin MCP transport and placement limits are [listed separately](environment-templates.md#environment-origin-mcp-plugins) |
| Input and configuration | Non-text initial input, broader content/configuration unions and reasoning/verbosity combinations; [structured output](structured-output.md) has a qualified Claude function profile, with other combinations remaining gaps |
| Input and configuration | Non-text initial input, broader content/configuration unions and reasoning/verbosity combinations; [structured output](structured-output.md) has qualified Claude function profiles on none and Core-managed Docker openai_hosted, with other combinations remaining gaps |
| Tools and interactions | [Deferred discovery qualification](tool-search.md), other tool types, effective tool-set enforcement and result/cancel publication ordering; MiniMax public functions and service-origin MCP remain unsupported |
| Vault and Credentials | OAuth/refresh, archive semantics, revocation/concurrent mutation and exact hosted selection/error behavior; static bearer CRUD/token replacement is already present |
| Existing resources | Full Item/SSE/Usage variants, omitted/null/default/error semantics, pagination and overlapping lifecycle behavior beyond recorded cases |
Expand Down
68 changes: 35 additions & 33 deletions contracts/agents-api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2698,39 +2698,41 @@ paths:
reject retries; known creators without recorded intent retain resolved-snapshot
retry rules. These conflict policies are local and not verified hosted parity.
Creation retries observe future events without replay; retry with stream=false
to retrieve the Session. Claude SDK environment:none supports qualified object-root
json_schema output with medium verbosity, single-Agent execution and ordinary
functions; other combinations remain unsupported. Other non-text initial input
remains unsupported. Basic Codex and Claude SDK openai_hosted creation requires
an explicitly configured managed provider. The Claude workspace profile supports
non-deferred function tools with text or successful inline PNG/JPEG results
alongside native workspace tools; HTTP MCP remains unsupported. Idle Sessions
provision automatically; initial provisioning has no caller connection action.
Network defaults to enabled; disabled and restricted exact ASCII hostnames
are supported. Restricted policy requires 1–100 allowed domains. Unsupported
hostname forms and startup installations are rejected. Confidential env, system/npm/Python
packages and ordered setup commands use the shared initialization lifecycle;
requested network applies after setup. Initial inline and tenant-owned file_id
files freeze encrypted bytes before provisioning, then install through the
common Core lifecycle before native execution or live Files access. Referenced
files/env/packages/setup overrides are rejected pending semantic verification.
Tenant-owned environment_template_id references inherit omitted network and
allow only narrowing overrides. Referenced network:null is explicitly unsupported
pending semantic verification. Core freezes effective configuration; template
updates/deletion do not alter Session snapshots or same-intent creation retries.
Inline or tenant-owned skill_reference Skills share initialization. Templates
preserve default/latest/explicit selectors; Session creation freezes concrete
metadata and encrypted content atomically. Skill-list omission inherits and
a supplied list replaces; null overrides and null version selectors remain
unqualified and reject. Source deletion/default updates cannot change committed
Session Skill contents. Deferred function discovery uses type-only tool_search
and per-function defer_loading in the qualified single-agent Claude environment:none
function profile, including qualified inline image messages and text results.
Explicit web_search mode disabled and programmatic_tool_calling enabled false
use frozen common Runtime controls. Enabled forms remain unqualified. Omitted
programmatic configuration preserves native behavior, a documented difference
from the official default-on behavior. Other combinations remain unqualified;
see the operation coverage.
to retrieve the Session. Claude SDK on none and Core-managed Docker openai_hosted
supports qualified object-root json_schema output with medium verbosity, single-Agent
execution and ordinary functions. Hosted execution reuses native workspace
tools and Files/Artifacts; Skills, Plugins, capability directories, HTTP MCP,
Subagent and tool_search combinations remain unqualified, including inherited
template contents. Other non-text initial input remains unsupported. Basic
Codex and Claude SDK openai_hosted creation requires an explicitly configured
managed provider. The Claude workspace profile supports non-deferred function
tools with text or successful inline PNG/JPEG results alongside native workspace
tools; HTTP MCP remains unsupported. Idle Sessions provision automatically;
initial provisioning has no caller connection action. Network defaults to
enabled; disabled and restricted exact ASCII hostnames are supported. Restricted
policy requires 1–100 allowed domains. Unsupported hostname forms and startup
installations are rejected. Confidential env, system/npm/Python packages and
ordered setup commands use the shared initialization lifecycle; requested
network applies after setup. Initial inline and tenant-owned file_id files
freeze encrypted bytes before provisioning, then install through the common
Core lifecycle before native execution or live Files access. Referenced files/env/packages/setup
overrides are rejected pending semantic verification. Tenant-owned environment_template_id
references inherit omitted network and allow only narrowing overrides. Referenced
network:null is explicitly unsupported pending semantic verification. Core
freezes effective configuration; template updates/deletion do not alter Session
snapshots or same-intent creation retries. Inline or tenant-owned skill_reference
Skills share initialization. Templates preserve default/latest/explicit selectors;
Session creation freezes concrete metadata and encrypted content atomically.
Skill-list omission inherits and a supplied list replaces; null overrides
and null version selectors remain unqualified and reject. Source deletion/default
updates cannot change committed Session Skill contents. Deferred function
discovery uses type-only tool_search and per-function defer_loading in the
qualified single-agent Claude environment:none function profile, including
qualified inline image messages and text results. Explicit web_search mode
disabled and programmatic_tool_calling enabled false use frozen common Runtime
controls. Enabled forms remain unqualified. Omitted programmatic configuration
preserves native behavior, a documented difference from the official default-on
behavior. Other combinations remain unqualified; see the operation coverage.
parameters:
- description: agents=v1
in: header
Expand Down
27 changes: 23 additions & 4 deletions contracts/agents-api/structured-output.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ the existing Agent/Session configuration resolution and immutable snapshot.

## Qualified execution profile

Claude SDK supports object-root schemas with `environment:none`, medium verbosity,
`multi_agent.enabled=false` and optional ordinary function tools returning text.
The native SDK remains responsible for its model/tool loop and schema validation.
Codex and MiniMax structured-output execution, workspace/HTTP MCP/Subagent
Claude SDK supports object-root schemas with `environment:none` or Core-managed
Docker `openai_hosted`, medium verbosity, `multi_agent.enabled=false` and optional
ordinary function tools returning text. Hosted execution uses the existing native
workspace tools, preparation, Files and Artifacts. The native SDK remains
responsible for its model/tool loop and schema validation. Codex and MiniMax
structured-output execution, self-hosted/E2B execution, Skills/Plugins/capability
directories (including inherited template contents), HTTP MCP, Subagent/tool-search
combinations and other root types are unqualified and explicitly rejected. These
are implementation gaps, not a redefinition of the official protocol.

Expand Down Expand Up @@ -38,6 +41,12 @@ a completed `final_answer` Message with the native tool-use ID and unchanged
and cancelled candidates cannot become a completed structured answer. No private
history read, output repair, schema coercion or prompt wrapper supplies the result.

Workspace preparation additionally verifies the installed bridge's
`workspace_structured_output` feature and complete local Runtime contract. Native
inventory includes `StructuredOutput` only when output configuration requests it;
root tool identity, abort checks, filesystem and credential protections remain
unchanged. A bundle feature alone does not qualify another public combination.

Recovery uses the existing Session/Turn/Items queries and native continuation.
SSE is still live-only. Frozen schemas apply to both initial and resumed execution;
ordinary text configuration retains its prior behavior.
Expand All @@ -52,6 +61,16 @@ covers a function-only random value, unchanged saved configuration, native resul
application receipts, ordered terminal SSE, persisted final JSON, daemon restart
and same-history continuation, cancellation, text override and tenant isolation.

`services/agents-api/tests/official_hosted_structured_native.py` extends public
acceptance to an independently deployed Core, dedicated PostgreSQL and Docker
Runtime using the real Kimi API. It covers initial saved configuration and inline
prepared configuration, function-only random values, active input receipts,
native file writes consistent with final JSON, Files/Artifact reads, unchanged
terminal SSE, result retries/conflicts, cold Core/Runtime continuation, pending
cancellation without a fabricated final, ordinary text and tenant/Session isolation.
The accepted image retains the pinned SDK 0.3.269 and Claude Code 2.1.269. This
qualifies that native/provider combination, not every model or schema dialect.

Focused tests cover native failure/retry projection, exact result bytes, schema
numeric admission, configuration transport and independent service qualification
for another harness. Running only these tests or importing the SDK is not a claim
Expand Down
Loading
Loading