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
9 changes: 9 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3013,6 +3013,15 @@ or containment of descendants that deliberately leave the group.

### Harness qualification and onboarding

`apps/parsar-daemon/internal/agent/harness.go` is the single source entry point
for Harness authors. Keep required lifecycle declarations, separate optional
interfaces and the existing registration methods there. Operation result types,
errors and Registry lookup/storage implementation may remain in focused files.
Use the existing `proto.SupportedAgentKind` and `AgentKindCapabilities` schema;
do not introduce a second capability descriptor or a combined optional interface.
Core service qualification remains separate from Runtime registration. Keep the
README and onboarding guide linked to this entry point.

Codex, Claude and future harnesses have equal architectural status. The common
Runtime wire protocol and Executor/Turn interfaces own lifecycle,
input receipts, cancellation, recovery and resource access; each native adapter
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ native differences.
| [API reference](docs/api/README.md) | The `/v1`, `/core/v1` and `/api/v1` namespaces |
| [Nodes and sandbox backends: operator reference](services/agents-api/HOSTED-SANDBOX-MANAGER.md) | Node protocol, manual registration, placement, maintenance |
| [Protocol coverage](contracts/agents-api/README.md) and [harness selection](contracts/agents-api/harness-selection.md) | Supported operations and native differences |
| [Add a Harness](contracts/agents-api/harness-onboarding.md) | Required adapter interfaces, lifecycle, capabilities, registration and acceptance |
| [Add a Harness](contracts/agents-api/harness-onboarding.md) | Start with [agent/harness.go](apps/parsar-daemon/internal/agent/harness.go): required interfaces, optional capabilities and registration; then follow the guide for acceptance |
| [Landing page source](site/index.html) | The public site |

### Maintainers and advanced deployments
Expand Down
61 changes: 0 additions & 61 deletions apps/parsar-daemon/internal/agent/executor.go

This file was deleted.

7 changes: 0 additions & 7 deletions apps/parsar-daemon/internal/agent/functions.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
package agent

import (
"context"
"errors"

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

var ErrUnknownFunctionCall = errors.New("agent: function call is no longer pending")

type FunctionResultSubmitter interface {
SubmitFunctionResult(context.Context, proto.FunctionResultPayload) error
}
220 changes: 220 additions & 0 deletions apps/parsar-daemon/internal/agent/harness.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// Package agent defines the Runtime's native Harness adapter contract.
// Start with this file when adding a Harness; the onboarding guide is at
// contracts/agents-api/harness-onboarding.md.
//
// Required lifecycle: ExecutorFactory prepares a fixed Session-owned Executor;
// each StartTurn returns a fresh Turn with its own output and settlement.
// The current public text profile also requires DurableSteerer. Keep optional
// interfaces separate and implement them only for qualified operations. MCP,
// images, structured output and Subagent observations use protocol messages
// rather than additional Go interfaces; qualify and advertise them separately.
//
// Registration: RegisterKind takes the existing proto.SupportedAgentKind
// descriptor (kind, availability, native version and AgentKindCapabilities) and
// direct-call Factory. RegisterExecutor supplies the Session-owned lifecycle.
// RegisterPreparation optionally adds workspace access without model input.
// RegisterKind resets the other factories, so register it first. Preparation
// flags are derived by these methods; other advertised capabilities must reflect
// verified native behavior. Built-in registration lives in internal/cli.
//
// Runtime registration and Core service qualification remain separate. A public
// Harness also needs a profile in services/agents-api/internal/engine; advertising
// a capability cannot authorize it. Requests, events and capability descriptors
// use the existing internal/agentdaemon/proto types.
package agent

import (
"context"

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

// Required execution lifecycle.

// ExecutorFactory prepares without model input. A failed factory retains any
// unconfirmed cleanup in its non-nil Executor.
type ExecutorFactory func(context.Context, proto.PromptRequestPayload) (Executor, error)

// Executor retains a fixed native configuration across independently owned Turns.
type Executor interface {
// A nil Turn guarantees no input was submitted or output retained and leaves
// out with the caller. A non-nil Turn owns out, including on error; input
// may have been submitted and must never be replayed automatically.
StartTurn(context.Context, string, proto.MessageInput, chan<- proto.Envelope) (Turn, error)
// Success confirms all native work and owned output streams have stopped.
// It does not establish an otherwise unknown Turn result. An error retains
// resource ownership; another caller may await or retry Close.
Close(context.Context) error
}

// Turn owns one output stream and never retargets cancellation to a successor.
type Turn interface {
Session
CancellationOutcome() proto.DonePayload
// Success confirms closed output and settled native input, function,
// interaction and child-work obligations. Errors cannot prove cancellation.
AwaitSettlement(context.Context) (TurnSettlement, error)
}

// TurnSettlement describes native readiness after this Turn has fully drained.
// A false result must carry a reason; it requires confirmed Executor.Close.
type TurnSettlement struct {
Reusable bool
Reason string
}

// Session is the cancellation surface shared by direct prompt runs and Turns.
// For Executor-owned Turns, AwaitSettlement and Executor.Close define settlement
// and resource retirement; Cancel alone does not transfer resource ownership.
type Session interface {
// Cancel signals the session to abort. Idempotent. Actual teardown
// happens asynchronously and is signalled via the out channel close.
Cancel(ctx context.Context) error
}

// Turn capabilities. Public profiles determine which operations are required.

// DurableSteerer reports one complete write synchronously, then waits for the native receipt.
type DurableSteerer interface {
SteerWithReceipt(context.Context, proto.PromptSteerPayload, func()) error
}

// Steerer optionally delivers additional text to the session's active turn.
type Steerer interface {
Steer(context.Context, proto.PromptSteerPayload) error
}

// FunctionResultSubmitter delivers a result for an outstanding native call.
// A successful return requires its native application receipt.
type FunctionResultSubmitter interface {
SubmitFunctionResult(context.Context, proto.FunctionResultPayload) error
}

// PermissionResponder optionally accepts decisions for emitted permission requests.
// Unknown or expired requests return ErrUnknownPermission.
type PermissionResponder interface {
SubmitPermission(context.Context, string, proto.PermissionDecisionPayload) error
}

// UserChoiceResponder optionally accepts answers for emitted user-choice requests.
// Unknown or expired requests return ErrUnknownAsk.
type UserChoiceResponder interface {
SubmitPromptForUserChoice(context.Context, string, proto.PromptForUserChoiceDecisionPayload) error
}

// Optional workspace capabilities, implemented by the authorized resource owner.

// WorkspaceReader returns success only after acknowledged native close on an existing owner.
type WorkspaceReader interface {
ReadWorkspaceFile(context.Context, string, int) (WorkspaceReadResult, error)
}

// WorkspaceDirectoryLister reads one directory through an existing workspace owner.
// An empty directory selects the root; other paths contain only relative components.
// Entry names are single components. Kind is file, directory, symlink or other;
// SizeBytes is present and nonnegative only for regular files. Results have no
// prescribed order, and Truncated must not be presented as a complete inventory.
// maxEntries is positive; adapters may reject limits above their private bound.
// Successful return requires settled directory/metadata access and handle cleanup.
// Implementations retain workspace authorization and isolation and use the existing
// WorkspaceRead errors for unsupported, unavailable, busy, invalid or uncertain reads.
// This interface does not establish public Files pagination or feature admission.
type WorkspaceDirectoryLister interface {
ListWorkspaceDirectory(context.Context, string, int) (WorkspaceDirectoryResult, error)
}

// WorkspaceWriter confirms a native commit on an already authorized prepared owner.
type WorkspaceWriter interface {
WriteWorkspaceFile(context.Context, string, []byte) (WorkspaceWriteResult, error)
}

// Separate preparation for qualified workspace access and direct-call paths.

// Prepared owns native resources until Start returns a non-nil Session. The
// preparation owner context spans the eventual Session; Start's context is local
// to that operation. A nil Session leaves preparation cleanup with the caller.
type Prepared interface {
// Start transfers output ownership only when it returns a non-nil Session.
// A nil Session leaves the caller as the sole owner of closing out, and the
// implementation must not retain or write to it after Start returns.
Start(context.Context, string, proto.MessageInput, chan<- proto.Envelope) (Session, error)
// Close retains unused ownership on error; callers may retry settlement.
Close() error
}

// PreparedCancellation is required for executable preparations and follows the
// same native resource across Start. Read-only preparations need only Prepared.
type PreparedCancellation interface {
Prepared
// Cancel returns after local cleanup and all output writes have stopped.
// An error retains ownership so callers can retry this exact object serially.
Cancel(context.Context) error
CancellationOutcome() proto.DonePayload
}

// A factory may return both a resource and an error when construction failed but
// cleanup remains unconfirmed. The caller must retain and close that resource.
type PreparationFactory func(context.Context, proto.PromptRequestPayload) (Prepared, error)

// Direct-call factory and registration. These use the existing Registry behavior.

// Factory builds a Session for one prompt_request. out is the upstream
// channel the agent writes into and closes exactly once after terminal output.
// ctx is cancelled by the router to wind the session down.
type Factory func(ctx context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope) (Session, error)

// RegisterKind installs f and the heartbeat descriptor for an
// agent_kind. Callers may set Available=false when an adapter exists
// but its underlying CLI is not usable.
func (r *Registry) RegisterKind(info proto.SupportedAgentKind, f Factory) {
kind := info.Kind
if kind == "" {
panic("agent.Registry.Register: empty kind")
}
if f == nil {
panic("agent.Registry.Register: nil factory")
}
r.mu.Lock()
defer r.mu.Unlock()
r.factories[kind] = f
delete(r.preparers, kind)
delete(r.executors, kind)
info.Capabilities.Preparation = false
info.Capabilities.WorkspaceReadPreparation = false
r.kinds[kind] = info
}

// RegisterExecutor installs the shared lifecycle after RegisterKind and derives
// the Preparation capability. It does not enable other public operations.
func (r *Registry) RegisterExecutor(kind string, factory ExecutorFactory) {
r.mu.Lock()
defer r.mu.Unlock()
info, exists := r.kinds[kind]
if !exists || factory == nil {
panic("agent.Registry.RegisterExecutor: registered kind and factory required")
}
r.executors[kind] = factory
info.Capabilities.Preparation = true
r.kinds[kind] = info
}

// RegisterPreparation installs a separate execution-only path. Product factory
// wrappers must not add authoring or capability-download side effects to it.
func (r *Registry) RegisterPreparation(kind string, workspaceRead bool, prepare PreparationFactory) {
r.mu.Lock()
defer r.mu.Unlock()
info, exists := r.kinds[kind]
if !exists || prepare == nil {
panic("agent.Registry.RegisterPreparation: registered kind and factory required")
}
r.preparers[kind] = prepare
info.Capabilities.Preparation = true
info.Capabilities.WorkspaceReadPreparation = workspaceRead
r.kinds[kind] = info
}

// Register installs f as the factory for kind with a basic available
// descriptor. Panics on empty kind or nil factory.
func (r *Registry) Register(kind string, f Factory) {
r.RegisterKind(proto.SupportedAgentKind{Kind: kind, Available: true}, f)
}
18 changes: 0 additions & 18 deletions apps/parsar-daemon/internal/agent/interactions.go

This file was deleted.

Loading
Loading