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
22 changes: 22 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -1934,6 +1934,28 @@ replaced; do not carry obsolete compatibility code forward to satisfy this secti
matching retries return committed state without replay. No Turn-level overrides,
provider catalog or self-hosted/none credential expansion is included. Public
input/null semantics and examples live in `contracts/agents-api/model-execution.md`.
- Session execution-configuration reads use a separate immutable safe projection,
written with provenance in the same creation transaction as Session resources.
Read no credential ciphertext and never recompute sources from current Agents
or operator defaults. Model/harness sources are independent; provider bundles
retain one source. Project reads redact all deployment provider details. Old
Sessions expose persisted model/harness with unknown sources and unavailable
provider metadata, without backfill. Projection metadata does not alter retry
identity; retries cannot replace it. Keep this query separate from runtime
observations and do not touch activity or wake sandboxes. The versioned contract
is `contracts/agents-api/execution-configuration.md`.
- Provider configuration support belongs to shared adapter-owned declarations in
`internal/harnessconfig`. One immutable declaration registry supplies public
provider validation, startup discovery and the optional safe provider descriptor
on native adapter registration. The separate engine catalog qualifies operations,
not a duplicate table of provider facts. Core owns credential environment and
endpoint admission policy, deployment enablement and safe HTTP projection. No parallel
HTTP harness table or readiness gate is permitted. The startup query includes
these declarations only with `include=configuration_capabilities`, preserving
its legacy default response for installed strict clients; the extension has an
independent schema version. Advertise build scope and unknown live availability,
not universal support across deployed Runtime versions. Operation-specific
admission remains authoritative; an extra registry declaration cannot enable it.
- Public Agent updates use `POST /v1/agents/{agent_id}` with the same tenant/Beta
boundary and shared saved-field validation. Preserve omission separately from
null; only supplied fields replace saved values. Metadata is a separate whole-map
Expand Down
24 changes: 24 additions & 0 deletions apps/parsar-daemon/internal/agent/provider_configuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package agent

import (
"cmp"
"slices"

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

func providerConfigurationDescriptor(registry harnessconfig.Registry, kind string) *proto.AgentProviderConfiguration {
configuration, known := registry.Lookup(kind)
if !known || len(configuration.Providers) == 0 {
return nil
}
descriptor := &proto.AgentProviderConfiguration{SchemaVersion: 1, Providers: make([]proto.AgentProviderProtocol, 0, len(configuration.Providers))}
for _, provider := range configuration.Providers {
descriptor.Providers = append(descriptor.Providers, proto.AgentProviderProtocol{
Protocol: provider.Protocol, RequiresTokenLimits: provider.RequiresTokenLimits,
})
}
slices.SortFunc(descriptor.Providers, func(a, b proto.AgentProviderProtocol) int { return cmp.Compare(a.Protocol, b.Protocol) })
return descriptor
}
84 changes: 84 additions & 0 deletions apps/parsar-daemon/internal/agent/provider_configuration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package agent_test

import (
"encoding/json"
"reflect"
"strings"
"testing"

"github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/agent"
"github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto"
"github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig"
"github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig/builtin"
)

func TestRegisteredAdaptersShareProviderDeclarations(t *testing.T) {
registry := agent.NewRegistry()
for _, kind := range builtin.Registry().Kinds() {
registry.RegisterKind(proto.SupportedAgentKind{Kind: kind, Available: false, Version: "fixture"}, stubFactory(kind))
}
for _, kind := range registry.SupportedAgentKinds() {
declaration, _ := builtin.Registry().Lookup(kind.Kind)
descriptor := kind.ProviderConfiguration
if kind.Available || descriptor == nil || descriptor.SchemaVersion != 1 || len(descriptor.Providers) != len(declaration.Providers) {
t.Fatalf("declaration changed readiness or was omitted: %#v", kind)
}
for _, provider := range descriptor.Providers {
rule, ok := declaration.Provider(provider.Protocol)
if !ok || rule.RequiresTokenLimits != provider.RequiresTokenLimits {
t.Fatalf("adapter registration disagrees with Core admission: %#v", provider)
}
}
}
}

func TestProviderDescriptorIsSafeDefensiveAndWireCompatible(t *testing.T) {
configuration := harnessconfig.Configuration{Providers: []harnessconfig.Provider{{Protocol: "fixture", RequiresTokenLimits: true}}}
declarations := harnessconfig.NewRegistry(map[string]harnessconfig.Configuration{"additional-adapter": configuration})
registry := agent.NewRegistryWithConfigurations(declarations)
registry.RegisterKind(proto.SupportedAgentKind{
Kind: "additional-adapter", Available: true,
ProviderConfiguration: &proto.AgentProviderConfiguration{SchemaVersion: 99, Providers: []proto.AgentProviderProtocol{{Protocol: "private-canary"}}},
}, stubFactory("additional-adapter"))
configuration.Providers[0].Protocol = "private-canary"
first := registry.SupportedAgentKinds()
first[0].ProviderConfiguration.Providers[0].Protocol = "private-canary"
first[0].ProviderConfiguration.SchemaVersion = 99
next := registry.SupportedAgentKinds()
if next[0].ProviderConfiguration.SchemaVersion != 1 || next[0].ProviderConfiguration.Providers[0].Protocol != "fixture" {
t.Fatal("caller mutated the adapter descriptor")
}
envelope, err := proto.NewEnvelope(proto.TypeHeartbeat, "", proto.HeartbeatPayload{SupportedAgentKinds: next})
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(envelope.Payload), "private-canary") {
t.Fatal("unregistered configuration leaked")
}
var roundTrip proto.HeartbeatPayload
if err := envelope.DecodePayload(&roundTrip); err != nil || !reflect.DeepEqual(roundTrip.SupportedAgentKinds, next) {
t.Fatalf("descriptor round trip failed: %v", err)
}
// The existing envelope decoder ignores added JSON fields; a peer built
// before this optional descriptor therefore still accepts the heartbeat.
var oldPeer struct {
Kinds []struct {
Kind string `json:"kind"`
Available bool `json:"available"`
Capabilities proto.AgentKindCapabilities `json:"capabilities"`
} `json:"supported_agent_kinds"`
}
if err := envelope.DecodePayload(&oldPeer); err != nil || len(oldPeer.Kinds) != 1 || oldPeer.Kinds[0].Kind != "additional-adapter" || !oldPeer.Kinds[0].Available {
t.Fatalf("older peer rejected the optional declaration: %v", err)
}
var oldDescriptor proto.SupportedAgentKind
if json.Unmarshal([]byte(`{"kind":"additional-adapter","available":true}`), &oldDescriptor) != nil || oldDescriptor.ProviderConfiguration != nil {
t.Fatal("missing declaration must remain unknown")
}
registry.Register("undeclared", stubFactory("undeclared"))
for _, kind := range registry.SupportedAgentKinds() {
if kind.Kind == "undeclared" && kind.ProviderConfiguration != nil {
t.Fatal("unknown adapter inherited support")
}
}
}
26 changes: 19 additions & 7 deletions apps/parsar-daemon/internal/agent/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"sync"

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

// Factory builds a Session for one prompt_request. out is the upstream
Expand Down Expand Up @@ -55,17 +57,25 @@ var ErrUnsupportedKind = errors.New("agent: unsupported agent_kind")
// Registry maps agent_kind → Factory and keeps the daemon-advertised
// capability descriptor for each kind. Safe for concurrent use.
type Registry struct {
mu sync.RWMutex
factories map[string]Factory
preparers map[string]PreparationFactory
kinds map[string]proto.SupportedAgentKind
mu sync.RWMutex
factories map[string]Factory
preparers map[string]PreparationFactory
kinds map[string]proto.SupportedAgentKind
configurations harnessconfig.Registry
}

func NewRegistry() *Registry {
return NewRegistryWithConfigurations(builtin.Registry())
}

// NewRegistryWithConfigurations shares an immutable adapter declaration snapshot
// with discovery and validation; factory registration still owns availability.
func NewRegistryWithConfigurations(configurations harnessconfig.Registry) *Registry {
return &Registry{
factories: make(map[string]Factory),
preparers: make(map[string]PreparationFactory),
kinds: make(map[string]proto.SupportedAgentKind),
factories: make(map[string]Factory),
preparers: make(map[string]PreparationFactory),
kinds: make(map[string]proto.SupportedAgentKind),
configurations: configurations,
}
}

Expand All @@ -92,6 +102,7 @@ func (r *Registry) RegisterKind(info proto.SupportedAgentKind, f Factory) {
delete(r.preparers, kind)
info.Capabilities.Preparation = false
info.Capabilities.WorkspaceReadPreparation = false
info.ProviderConfiguration = providerConfigurationDescriptor(r.configurations, kind)
r.kinds[kind] = info
}

Expand Down Expand Up @@ -129,6 +140,7 @@ func (r *Registry) SupportedAgentKinds() []proto.SupportedAgentKind {
if info.Kind == "" {
info = proto.SupportedAgentKind{Kind: kind, Available: true}
}
info.ProviderConfiguration = info.ProviderConfiguration.Clone()
out = append(out, info)
}
slices.SortFunc(out, func(a, b proto.SupportedAgentKind) int {
Expand Down
7 changes: 7 additions & 0 deletions apps/parsar-daemon/internal/cli/claude_sdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ func TestClaudeSDKDiscoveryAndRegistration(t *testing.T) {
t.Fatal(info)
}
for _, registered := range reg.SupportedAgentKinds() {
if registered.Kind == "claude_sdk" {
configuration := registered.ProviderConfiguration
if configuration == nil || configuration.SchemaVersion != 1 || len(configuration.Providers) != 1 || configuration.Providers[0].Protocol != "anthropic" || configuration.Providers[0].RequiresTokenLimits {
t.Fatal("SDK registration lost its provider declaration")
}
registered.ProviderConfiguration = nil
}
if registered.Kind == "claude_sdk" && registered != info {
t.Fatalf("SDK descriptor changed: %+v", registered)
}
Expand Down
92 changes: 92 additions & 0 deletions contracts/agents-api/execution-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Execution configuration queries

These read-only Core extensions describe configuration, not execution health.
They require the existing project bearer authentication and `OpenAI-Beta:
agents=v1`. They never contact a model provider, start a Turn or wake a sandbox.

## Frozen Session selections

`GET /v1/agents/sessions/{session_id}/execution-configuration` returns:

```json
{
"object": "agent.session.execution_configuration",
"schema_version": 1,
"session_id": "013773a9-44b9-4f84-baca-b51c04a01201",
"model": {"value": "requested-model", "source": "session"},
"harness": {"value": "codex", "source": "agent"},
"model_provider": {
"source": "agent",
"status": "available",
"configuration": {
"protocol": "responses",
"base_url": "https://model.example/v1",
"api_key_configured": true
}
}
}
```

`source` is `session`, `agent`, `deployment` or `unknown`. Model and harness
sources are independent; a model-only override can retain an Agent's harness and
whole provider bundle. Explicit inline harness selection is `session`; a null
inline Agent extension resets the harness to `deployment` without clearing the
inherited provider. A null Session provider inherits normally. Model is required
inline or inherited from a saved Agent; it has no implicit deployment default.

The provider's `status` describes visibility:

- `available`: a safe Session- or Agent-supplied bundle. Configuration contains
protocol, endpoint, configured-key flag and optional token limits.
- `redacted`: deployment-owned configuration. Source is `deployment` and
configuration is null. Project bearer credentials do not grant deployment
secret access; this API has no endpoint-reveal override.
- `unavailable`: no trustworthy safe provider projection was recorded. Source is
`unknown` and configuration is null. This does not mean that execution failed.

New public Session creations save a minimal safe projection and provenance in the
same transaction as the Session and its private credential snapshot. Reads select
only that safe row and immutable Session configuration, without decrypting secrets
or re-resolving an Agent or deployment defaults. Agent edits/deletion, deployment
changes, Core restart and suspend/resume cannot change the view. Same-key retries
return the original Session and do not repair or overwrite its provenance.

Historical Sessions without the projection report their persisted model/harness
when present, with `unknown` sources; a missing value is null. Their provider view
is `unavailable`, even if a private credential row exists. No backfill guesses
provenance. Missing, deleted and foreign-tenant Session IDs share the existing
not-found response. Responses are `Cache-Control: no-store`. Keys, ciphertext,
secret references, native headers, query parameters and permissions are excluded.

## Supported provider configuration

`GET /v1/agents/core/startup-configuration?include=configuration_capabilities`
adds a `configuration_capabilities` field to the existing startup view. Without
that exact optional query, the legacy response shape is unchanged, including its
schema version. Duplicate, unknown or malformed startup queries are rejected.
The extension has its own `schema_version: 1`:

- `scope: core_build_provider_configuration` identifies the limited subject.
- `runtime_availability: unknown` explicitly excludes live peer readiness.
- `admission` declares Core's hosted-only credential boundary, HTTPS endpoint
restrictions and nonnegative ordered token limits.
- `harnesses` is sorted by registered harness name. Each entry contains
`support` (`supported` or `unknown`), deployment `enabled` and `default` flags,
plus provider protocol declarations. An unknown declaration has no providers.
- Each provider lists `required_fields` and `positive_fields`. Codex declares
Responses; Claude SDK and MiniMax Code declare Anthropic. MiniMax Code also
requires positive `context_window` and `max_output_tokens`.

Adapter-owned declarations in `internal/harnessconfig` have one immutable registry
used by provider validation and discovery. Native adapter registration attaches
the same optional safe provider descriptor to the common Runtime contract. The
separate engine catalog still owns qualification of public operations. Core
composition adds deployment enablement and Core-owned admission restrictions, then projects
only public fields. It does not serialize native capability or operator objects.
Adding a declaration does not authorize a new public operation. Runtime versions
may differ; actual selection/admission still checks the chosen Runtime's features.
No node, binary, endpoint, model or key readiness is implied by support or enablement.

The endpoints do not mutate configuration, rotate keys, migrate Sessions, expose
a model catalog or accept arbitrary native options. See
[model-execution.md](model-execution.md) for write and inheritance semantics.
Loading
Loading