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
25 changes: 22 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,10 @@ 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.
Execution and Files use the same authorized workspace through the existing
Core/Runtime contract. Native tool calls stay local. Process placement and native
Core/Runtime contract. Native tool calls stay local.
CLI discovery uses a bounded 15-second version probe per installed harness;
missing binaries fail immediately. A version result is availability, not Environment
readiness, and does not change initialization or connection ownership. Process placement and native
transport remain adapter responsibilities, without a second model/tool loop.
The former separated Runtime/harness and workspace executor topology is a distant
future V2 option, to revisit only after V1 is stable and concrete needs justify it.
Expand Down Expand Up @@ -288,13 +291,29 @@ Completed environments never reinstall initial files on reconnect or native reco
Provider RunCommand carries bounded stdin, not confidential argv. Only fixed trusted
initializers may run with Runtime authority. User setup and package install hooks
run in the common packaged sandbox, without daemon credentials or native history.
Files, inline Skills and npm/Python packages precede ordered setup commands. Initialization has
Files and inline Skills precede system, npm/Python packages and ordered setup commands. Initialization has
provisioning network access; requested network restrictions apply to native tools
after setup. Confidential env and setup snapshots are encrypted independently of
ordinary metadata. Adapters apply tool env only after isolation, never to the
credential-bearing daemon/native harness launcher.
Reuse the packaged atomic file writer and anchored parent creation across all profiles.

System packages use one Runtime-owned tool root, separate from trusted daemon and
harness executables. Build its immutable seed from the base image before adding
Runtime/harness code or secrets; include the matching package database and base
tool symlink targets. The shared installer extracts independent inodes and runs
apt/dpkg inside an unprivileged namespace. Package scripts cannot access Runtime
credentials, native history or outer processes. Later setup and native tools enter
the installed root read-only, retaining the authorized workspace and adapter-owned
scratch. `/workspace` and `/environment/workspace` refer to the same authorized
workspace inside that root, preserving native working directories. Native adapters
own entry and existing process cancellation; Core never
selects an engine or Provider for package initialization. No live filesystem
snapshot, second lifecycle owner or package-manager framework is introduced.
Core preserves the system-package requirement in the common execution binding;
a missing installation receipt fails preparation instead of falling back to base
tools. This requirement does not add execution prerequisites to Files reads.

Inline Skill ZIPs use the same confidential initialization snapshot and installer.
Core validates portable manifests and bounded regular-file archives, returns only
safe Skill metadata, and freezes content before native preparation. The Runtime
Expand All @@ -307,7 +326,7 @@ unqualified. Skills API references, generic Plugins and capability-directory
imports remain separate work; an adapter-owned Claude plugin envelope does not
implement public Plugins.

Name, enabled/disabled network, initial files, inline Skills and env/setup/npm/Python are
Name, enabled/disabled network, initial files, inline Skills and env/setup/system/npm/Python are
implemented independently of remaining installation fields. Reject unsupported
inputs rather than persisting them for silent
omission; expand inline and template initialization together in separately qualified
Expand Down
4 changes: 3 additions & 1 deletion apps/parsar-daemon/internal/agent/claudesdk/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type WorkspaceConfig struct {
type workspaceProfile struct {
Skills []agentskill.Metadata `json:"skills,omitempty"`
ToolEnvironment bool `json:"tool_environment,omitempty"`
SystemPackages bool `json:"system_packages,omitempty"`
Home string `json:"home"`
State string `json:"state"`
Scratch string `json:"scratch"`
Expand All @@ -54,10 +55,11 @@ func prepareWorkspace(config Config, req proto.PromptRequestPayload) (*workspace
return nil, nil, err
}
if req.LocalEnvironment != nil && req.LocalEnvironment.ToolEnvironment {
if err := localworkspace.VerifyToolEnvironment(); err != nil {
if err := localworkspace.VerifyToolEnvironment(req.LocalEnvironment.SystemPackages); err != nil {
return nil, nil, err
}
profile.ToolEnvironment = true
profile.SystemPackages = req.LocalEnvironment.SystemPackages
}
if req.LocalEnvironment != nil {
if err := localworkspace.VerifySkills(req.LocalEnvironment.Skills); err != nil {
Expand Down
9 changes: 8 additions & 1 deletion apps/parsar-daemon/internal/agent/codex/session_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,18 @@ func prepareSessionPlan(ctx context.Context, req proto.PromptRequestPayload, cfg
}

if req.LocalEnvironment != nil && req.LocalEnvironment.ToolEnvironment {
if err := localworkspace.VerifyToolEnvironment(); err != nil {
if err := localworkspace.VerifyToolEnvironment(req.LocalEnvironment.SystemPackages); err != nil {
plan.Cleanup()
return SessionPlan{}, "", err
}
plan.Env = append(plan.Env, "PARSAR_RUNTIME_TOOL_ENV=1")
if req.LocalEnvironment.SystemPackages {
if err := prepareSystemToolAnchor(); err != nil {
plan.Cleanup()
return SessionPlan{}, "", err
}
plan.Env = append(plan.Env, "PARSAR_RUNTIME_SYSTEM_PACKAGES=1")
}
plan.ExtraConfig = append(plan.ExtraConfig, [2]string{"features.hooks", "true"})
}

Expand Down
15 changes: 15 additions & 0 deletions apps/parsar-daemon/internal/agent/codex/tool_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@ import (
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
"time"
)

const toolEnvironmentHookSource = "/etc/codex/runtime-hooks"
const toolEnvironmentHookCommand = "/usr/bin/python3 -I -S /etc/codex/tool-env.py"

func prepareSystemToolAnchor() error {
const anchor = "/tmp/parsar-tool-root"
if err := os.Mkdir(anchor, 0500); err != nil && !errors.Is(err, os.ErrExist) {
return errors.New("codex: system tool temporary anchor unavailable")
}
actual, err := filepath.EvalSymlinks(anchor)
entries, readErr := os.ReadDir(anchor)
if err != nil || actual != anchor || readErr != nil || len(entries) != 0 {
return errors.New("codex: system tool temporary anchor is not an empty canonical directory")
}
return nil
}

func verifyToolEnvironmentHook(ctx context.Context, rpc *JSONRPCClient, cwd string) error {
operation, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
Expand Down
3 changes: 2 additions & 1 deletion apps/parsar-daemon/internal/agent/mcode/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,11 @@ func prepareWorkspaceOptions(ctx context.Context, c WorkspaceConfig, req proto.P
}
profile := map[string]any{"workspace": "/workspace", "scratch": c.Scratch, "network": c.Network, "protectedDirs": slices.Clone(c.ProtectedDirs), "skills": len(req.LocalEnvironment.Skills) > 0}
if req.LocalEnvironment.ToolEnvironment {
if err := localworkspace.VerifyToolEnvironment(); err != nil {
if err := localworkspace.VerifyToolEnvironment(req.LocalEnvironment.SystemPackages); err != nil {
return opts, err
}
profile["toolEnvironment"] = true
profile["systemPackages"] = req.LocalEnvironment.SystemPackages
// Initialization exposes only user env/packages; private staging and
// daemon/native history remain explicitly denied.
protected := slices.Clone(c.ProtectedDirs)
Expand Down
2 changes: 1 addition & 1 deletion apps/parsar-daemon/internal/cli/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
const (
// cliVersionTimeout caps CLI `--version` preflights so a hung agent
// binary can't keep `parsar-daemon connect` blocked at startup.
cliVersionTimeout = 5 * time.Second
cliVersionTimeout = 15 * time.Second

bootstrapTimeout = 10 * time.Second

Expand Down
5 changes: 4 additions & 1 deletion apps/parsar-daemon/internal/localworkspace/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,11 @@ func (b *Binding) Configure(r proto.PromptRequestPayload) (proto.PromptRequestPa
return r, errors.New("request does not match the local Runtime network policy")
}
if !r.WorkspaceReadOnly {
if r.LocalEnvironment.SystemPackages && !r.LocalEnvironment.ToolEnvironment {
return r, errors.New("system packages require initialized tool configuration")
}
if r.LocalEnvironment.ToolEnvironment {
if err := VerifyToolEnvironment(); err != nil {
if err := VerifyToolEnvironment(r.LocalEnvironment.SystemPackages); err != nil {
return r, err
}
}
Expand Down
23 changes: 20 additions & 3 deletions apps/parsar-daemon/internal/localworkspace/initialization.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package localworkspace

import (
"encoding/json"
"errors"
"os"
"path/filepath"
Expand All @@ -12,24 +13,40 @@ const (
ToolEnvironmentShell = InitializationDirectory + "/tool-env.sh"
ToolEnvironmentJSON = InitializationDirectory + "/tool-env.json"
PackageDirectory = "/environment/packages"
SystemPackageDirectory = PackageDirectory + "/system"
SystemPackageReceipt = InitializationDirectory + "/system-root.json"
SystemToolLauncher = "/usr/local/bin/agents-api-tool-root"
)

// VerifyToolEnvironment is required only for execution consuming initialized
// tool configuration. It never makes Files reads depend on execution setup.
func VerifyToolEnvironment() error {
for _, path := range []string{InitializationDirectory, PackageDirectory, ToolEnvironmentShell, ToolEnvironmentJSON} {
func VerifyToolEnvironment(systemPackages bool) error {
paths := []string{InitializationDirectory, PackageDirectory, ToolEnvironmentShell, ToolEnvironmentJSON}
if systemPackages {
paths = append(paths, SystemPackageDirectory, SystemPackageReceipt, SystemToolLauncher)
}
for _, path := range paths {
actual, err := filepath.EvalSymlinks(path)
info, statErr := os.Lstat(path)
if err != nil || statErr != nil || actual != path {
return errors.New("initialized tool configuration unavailable")
}
if path == InitializationDirectory || path == PackageDirectory {
if path == InitializationDirectory || path == PackageDirectory || path == SystemPackageDirectory {
if !info.IsDir() {
return errors.New("initialized tool directory unavailable")
}
} else if !info.Mode().IsRegular() || info.Mode().Perm()&0222 != 0 || info.Size() > 1024*1024 {
return errors.New("initialized tool configuration is not immutable")
}
}
if systemPackages {
raw, err := os.ReadFile(SystemPackageReceipt)
var receipt struct {
Version int `json:"version"`
}
if err != nil || json.Unmarshal(raw, &receipt) != nil || receipt.Version != 1 {
return errors.New("installed system tools unavailable")
}
}
return nil
}
12 changes: 6 additions & 6 deletions contracts/agents-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ user-managed enrollment remain outside this qualification.
| Area | Missing or unverified scope |
| --- | --- |
| Subagents / multi_agent | Six public child read operations, enabled execution, child lifecycle/interactions and full recovery; deferred outside the MVP |
| Environment Templates | Skills references, Plugins, system packages, capability directories, restricted network, installation overrides/null network and exact hosted errors; CRUD/list, files, env/setup/npm/Python, inline Skills and Session references are supported |
| Environment Templates | Skills references, Plugins, capability directories, restricted network, installation overrides/null network and exact hosted errors; CRUD/list, files, env/setup/system/npm/Python, inline Skills and Session references are supported |
| Input and configuration | Non-text initial input, broader content/configuration unions, structured output and reasoning/verbosity combinations |
| Tools and interactions | Deferred functions, other tool types, effective tool-set enforcement and result/cancel publication ordering; MiniMax public functions/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 |
Expand Down Expand Up @@ -355,7 +355,7 @@ including further deployment qualification; this inventory describes merged beha
describe native discovery or workspace files created by commands. Unknown
installation configurations are rejected, not reported as empty. Reads use the
owning live Session's project partition and do not require execution setup.
System packages and remaining unsupported installation configuration, full hosted lifecycle and
Remaining unsupported installation configuration, full hosted lifecycle and
exact hosted error semantics remain gaps.

[Environment Templates](environment-templates.md) provide tenant-owned CRUD/list
Expand Down Expand Up @@ -399,10 +399,10 @@ operator setup: [Codex](../../services/agents-api/deploy/codex/README.md),
[MiniMax Code](../../services/agents-api/deploy/mcode/README.md). The
[E2B guide](../../services/agents-api/deploy/e2b/README.md) packages those qualified
images as pinned templates.
The env/setup/npm/Python initialization batch extends these profiles; see its
[current evidence boundary](environment-templates.md#current-setup-batch). System
packages, remaining unsupported startup installations, restricted domains and hosted public
HTTP MCP remain outside these accepted profiles. MiniMax's private MCP tool bridge
The shared initialization path supports env/setup and system/npm/Python packages;
see the [evidence and limits](environment-templates.md#verification). Remaining
unsupported startup installations, restricted domains and hosted public HTTP MCP
remain outside these accepted profiles. MiniMax's private MCP tool bridge
is internal transport, not public MCP support.

The [Codex self-hosted profile](environments.md) remains distinct from managed
Expand Down
Loading
Loading