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
1 change: 1 addition & 0 deletions services/core/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Domain owners, each with its PostgreSQL adapter under `internal/persistence/post
- `agents` (`agentpg`): saved Agents, their configuration merge and bounds, and the encrypted model-provider bundle bound to each Agent.
- `files` (`filepg`): source Files.
- `vaults` (`vaultpg`): Vaults and Credentials, the encryption of Credential secrets, OAuth access-token refresh, and the MCP credential selection that Session creation freezes and the Dispatcher's `Credentials` resolves into a bearer token.
- `environmenttemplates` (`templatepg`): Environment Templates, their validation and default network, their sealed setup, initial files, Skills and Plugins, and the resolved Template that Session creation composes into its Environment.

## Request handling

Expand Down
3 changes: 2 additions & 1 deletion services/core/cmd/server/http_routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ func daemonComposition(t testing.TB) http.Handler {
Engine: "codex", CoreKeys: admin, InstallationBindings: struct{ api.InstallationBindings }{},
Projects: trapProjects{keys: keys}, ModelProviders: struct{ api.ModelProviders }{},
Vaults: struct{ api.Vaults }{}, VaultsReader: struct{ api.VaultsReader }{},
Files: struct{ api.Files }{}, FilesReader: struct{ api.FilesReader }{}, Skills: struct{ api.Skills }{}, EnvironmentTemplates: struct{ api.EnvironmentTemplates }{},
Files: struct{ api.Files }{}, FilesReader: struct{ api.FilesReader }{}, Skills: struct{ api.Skills }{},
Agents: struct{ api.Agents }{}, AgentsReader: struct{ api.AgentsReader }{}, Sessions: struct{ api.Sessions }{}, SessionEvents: struct{ api.SessionEvents }{},
EnvironmentTemplates: struct{ api.EnvironmentTemplates }{}, EnvironmentTemplatesReader: struct{ api.EnvironmentTemplatesReader }{},
SessionHistory: struct{ api.SessionHistory }{}, Subagents: struct{ api.Subagents }{}, Artifacts: struct{ api.Artifacts }{},
SessionAdmin: struct{ api.SessionAdmin }{}, Environments: struct{ api.Environments }{}, ExecutorConnections: struct{ api.ExecutorConnections }{},
Admin: struct{ api.Admin }{}, AdminAudit: struct{ api.AdminAudit }{}, WriteAudit: struct{ api.WriteAudit }{}, Metrics: struct{ api.Metrics }{},
Expand Down
10 changes: 9 additions & 1 deletion services/core/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ import (
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/api"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/coremetrics"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/databaseurl"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmenttemplates"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/execution"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/files"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/nativeinstaller"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/persistence/postgres/agentpg"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/persistence/postgres/auditpg"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/persistence/postgres/filepg"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/persistence/postgres/pgunit"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/persistence/postgres/templatepg"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/persistence/postgres/vaultpg"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtime"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtimeenrollment"
Expand Down Expand Up @@ -125,6 +127,11 @@ func run() error {
if err != nil {
return err
}
templateStore := templatepg.New(units, credentialKey)
environmentTemplates, err := environmenttemplates.NewService(templateStore)
if err != nil {
return err
}
installation, err := installationFacts(public)
if err != nil {
return err
Expand Down Expand Up @@ -318,7 +325,8 @@ func run() error {
Installation: installation, InstallationBindings: executionStore,
Projects: executionStore, ModelProviders: executionStore,
Vaults: vaultService, VaultsReader: vaultStore,
Skills: executionStore, EnvironmentTemplates: executionStore,
Skills: executionStore,
EnvironmentTemplates: environmentTemplates, EnvironmentTemplatesReader: templateStore,
Files: fileService, FilesReader: fileStore,
Agents: agentService, AgentsReader: agentStore,
Sessions: executionStore, SessionEvents: executionStore, SessionHistory: executionStore,
Expand Down
5 changes: 4 additions & 1 deletion services/core/internal/api/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ type Dependencies struct {
RuntimeObservations RuntimeObservations
RuntimeHistory RuntimeHistory

EnvironmentTemplatesReader EnvironmentTemplatesReader

// Execution is nil when this Core runs without a Runtime gateway, and so
// without an execution Worker. Work that needs one then answers 503
// execution_unavailable.
Expand Down Expand Up @@ -116,8 +118,9 @@ func (d Dependencies) validate() error {
field{"Vaults", d.Vaults}, field{"VaultsReader", d.VaultsReader},
field{"ModelProviders", d.ModelProviders}, field{"Skills", d.Skills},
field{"Files", d.Files}, field{"FilesReader", d.FilesReader},
field{"EnvironmentTemplates", d.EnvironmentTemplates}, field{"EnvironmentTemplatesReader", d.EnvironmentTemplatesReader},
field{"Agents", d.Agents}, field{"AgentsReader", d.AgentsReader},
field{"EnvironmentTemplates", d.EnvironmentTemplates}, field{"Sessions", d.Sessions},
field{"Sessions", d.Sessions},
field{"SessionEvents", d.SessionEvents}, field{"SessionHistory", d.SessionHistory}, field{"Subagents", d.Subagents},
field{"Artifacts", d.Artifacts}, field{"SessionAdmin", d.SessionAdmin}, field{"Environments", d.Environments},
field{"ExecutorConnections", d.ExecutorConnections}, field{"Admin", d.Admin}, field{"AdminAudit", d.AdminAudit}, field{"WriteAudit", d.WriteAudit},
Expand Down
8 changes: 6 additions & 2 deletions services/core/internal/api/dependencies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type testFakes struct {
deployment *fakeDeployment
deploymentChanges *fakeDeploymentChanges
configurationDiscovery *fakeConfigurationDiscovery

environmentTemplatesReader *fakeEnvironmentTemplatesReader
}

// testDependencies returns Dependencies in which every area is a strict fake.
Expand All @@ -58,7 +60,8 @@ func testDependencies(t testing.TB) (Dependencies, *testFakes) {
f := &testFakes{
projects: &fakeProjects{t: t}, modelProviders: &fakeModelProviders{t: t},
vaults: &fakeVaults{t: t}, vaultsReader: &fakeVaultsReader{t: t},
skills: &fakeSkills{t: t}, environmentTemplates: &fakeEnvironmentTemplates{t: t},
skills: &fakeSkills{t: t},
environmentTemplates: &fakeEnvironmentTemplates{t: t}, environmentTemplatesReader: &fakeEnvironmentTemplatesReader{t: t},
files: &fakeFiles{t: t}, filesReader: &fakeFilesReader{t: t},
agents: &fakeAgents{t: t}, agentsReader: &fakeAgentsReader{t: t},
sessions: &fakeSessions{t: t}, sessionEvents: &fakeSessionEvents{t: t},
Expand All @@ -74,8 +77,9 @@ func testDependencies(t testing.TB) (Dependencies, *testFakes) {
Projects: f.projects, ModelProviders: f.modelProviders, Skills: f.skills,
Vaults: f.vaults, VaultsReader: f.vaultsReader,
Files: f.files, FilesReader: f.filesReader,
EnvironmentTemplates: f.environmentTemplates, EnvironmentTemplatesReader: f.environmentTemplatesReader,
Agents: f.agents, AgentsReader: f.agentsReader,
EnvironmentTemplates: f.environmentTemplates, Sessions: f.sessions, SessionEvents: f.sessionEvents,
Sessions: f.sessions, SessionEvents: f.sessionEvents,
SessionHistory: f.sessionHistory, Subagents: f.subagents, Artifacts: f.artifacts, SessionAdmin: f.sessionAdmin,
Environments: f.environments, ExecutorConnections: f.executorConnections, Admin: f.admin, AdminAudit: f.adminAudit, WriteAudit: f.writeAudit,
Metrics: f.metrics, RuntimeObservations: f.runtimeObservations, RuntimeHistory: f.runtimeHistory,
Expand Down
8 changes: 4 additions & 4 deletions services/core/internal/api/environment_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"reflect"
"testing"

"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmenttemplates"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store"
)

Expand All @@ -15,7 +16,7 @@ func TestRestrictedNetworkPublicMetadataPreservesInput(t *testing.T) {
if err != nil || !in.SetNetwork || !reflect.DeepEqual(in.AllowedDomains, domains) {
t.Fatal("template input changed", in, err)
}
response := templateResponse(store.EnvironmentTemplate{NetworkAccess: in.NetworkAccess, AllowedDomains: in.AllowedDomains})
response := templateResponse(environmenttemplates.Template{NetworkAccess: in.NetworkAccess, AllowedDomains: in.AllowedDomains})
if response.Network.Access != "restricted" || !reflect.DeepEqual(response.Network.AllowedDomains, domains) {
t.Fatal("template response changed", response.Network)
}
Expand Down Expand Up @@ -46,8 +47,7 @@ func TestRestrictedNetworkPublicMetadataPreservesInput(t *testing.T) {
}

func TestTemplateNetworkOverridesOnlyNarrowAndRetainIntent(t *testing.T) {
lookup := &templateLookupStore{network: "restricted", domains: []string{"Example.com", "api.example.com", "example.com"}}
h := templateHandler(t, lookup.ResolveEnvironmentTemplate)
lookup := &templateLookup{network: "restricted", domains: []string{"Example.com", "api.example.com", "example.com"}}
for _, test := range []struct {
name, override string
want []string
Expand Down Expand Up @@ -75,7 +75,7 @@ func TestTemplateNetworkOverridesOnlyNarrowAndRetainIntent(t *testing.T) {
if err != nil {
t.Fatal(err)
}
err = h.resolveTemplateEnvironment(t.Context(), "tenant", &input)
err = applyTemplateEnvironment(&input, lookup.resolved())
if test.invalid {
if err == nil {
t.Fatal("template authority widened")
Expand Down
9 changes: 4 additions & 5 deletions services/core/internal/api/environment_plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestPluginsSharedParsingConfidentialMetadataAndOverrides(t *testing.T) {
plugin := pluginInput(t)
raw := []byte(`{"plugins":[` + string(plugin) + `],"capability_directories":["/workspace/generated"]}`)
template, err := decodeTemplateInput(raw)
if err != nil || !template.SetPlugins || !template.SetDirectories || len(template.Initialization.Plugins) != 1 {
if err != nil || !template.SetPlugins || !template.SetDirectories || len(template.Setup.Plugins) != 1 {
t.Fatal("template", err)
}
var decoded decodedSessionRequest
Expand All @@ -55,7 +55,7 @@ func TestPluginsSharedParsingConfidentialMetadataAndOverrides(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(input.initialization.Plugins, template.Initialization.Plugins) {
if !reflect.DeepEqual(input.initialization.Plugins, template.Setup.Plugins) {
t.Fatal("different installation inputs")
}
cfg, err := resolve(input, "tenant", "key", nil)
Expand All @@ -79,8 +79,7 @@ func TestPluginsSharedParsingConfidentialMetadataAndOverrides(t *testing.T) {
if response, err := hostedSessionEnvironment(env); err != nil || len(*response.Plugins) != 1 || len(*response.CapabilityDirectories) != 1 {
t.Fatal("session response", err)
}
lookup := &templateLookupStore{network: "enabled", plugins: template.Initialization.Plugins, directories: template.Initialization.CapabilityDirectories}
h := templateHandler(t, lookup.ResolveEnvironmentTemplate)
lookup := &templateLookup{network: "enabled", plugins: template.Setup.Plugins, directories: template.Setup.CapabilityDirectories}
for _, override := range []string{"", `,"plugins":null,"capability_directories":null`, `,"plugins":[],"capability_directories":[]`} {
if err = json.Unmarshal([]byte(`{"agent":{"model":"test"},"environment":{"type":"openai_hosted","environment_template_id":"template"`+override+`}}`), &decoded); err != nil {
t.Fatal(err)
Expand All @@ -93,7 +92,7 @@ func TestPluginsSharedParsingConfidentialMetadataAndOverrides(t *testing.T) {
if err != nil || !bytes.Contains(intent, []byte("template")) {
t.Fatal("intent", err)
}
if err = h.resolveTemplateEnvironment(t.Context(), "tenant", &in); err != nil {
if err = applyTemplateEnvironment(&in, lookup.resolved()); err != nil {
t.Fatal(err)
}
want := 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ func TestPreparationTemplateSharedAcrossPlacements(t *testing.T) {
if err != nil {
t.Fatal(err)
}
h := templateHandler(t, compositionFixture().ResolveEnvironmentTemplate)
template := compositionFixture()
for _, input := range []*sessionRequest{&hosted, &own} {
if err := h.resolveTemplateEnvironment(t.Context(), "tenant", input); err != nil {
if err := applyTemplateEnvironment(input, *template); err != nil {
t.Fatal(err)
}
}
Expand Down
6 changes: 3 additions & 3 deletions services/core/internal/api/environment_setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
func TestEnvironmentSetupSharedParsingAndConfidentialSnapshot(t *testing.T) {
raw := []byte(`{"env":{"TOKEN":"private-env-canary","QUOTED":"'\n$(false)"},"packages":{"npm":["is-number@7.0.0"],"python":["packaging==26.0"]},"setup_commands":[{"command":"printf private-command-canary > result","cwd":null},{"command":"pwd","cwd":"/workspace/sub"}]}`)
input, err := decodeTemplateInput(raw)
if err != nil || !input.SetEnv || !input.SetSetup || !input.SetPackages || len(input.Initialization.Commands) != 2 {
if err != nil || !input.SetEnv || !input.SetCommands || !input.SetPackages || len(input.Setup.Commands) != 2 {
t.Fatal("template setup input", err)
}
var request decodedSessionRequest
Expand All @@ -24,7 +24,7 @@ func TestEnvironmentSetupSharedParsingAndConfidentialSnapshot(t *testing.T) {
if err != nil || bytes.Contains(configuration, []byte("canary")) || !bytes.Contains(configuration, []byte("is-number@7.0.0")) {
t.Fatal("confidential input in ordinary configuration", err)
}
if decoded.initialization.Env["TOKEN"] != input.Initialization.Env["TOKEN"] || len(decoded.initialization.Commands) != 2 {
if decoded.initialization.Env["TOKEN"] != input.Setup.Env["TOKEN"] || len(decoded.initialization.Commands) != 2 {
t.Fatal("inline and template parsing diverged")
}
for _, invalid := range []string{`{"env":{"OPENAI_API_KEY":"x"}}`, `{"env":{"CODEX_HOME":"x"}}`, `{"env":{"OAC_RUNTIME_HOME":"x"}}`, `{"env":{"BAD-NAME":"x"}}`, `{"env":{"VALUE":null}}`, `{"setup_commands":[null]}`, `{"setup_commands":[{}]}`, `{"setup_commands":[{"command":null}]}`, `{"setup_commands":[{"command":"pwd","cwd":""}]}`, `{"packages":{"python":[null]}}`, `{"packages":{"npm":["--ignore-scripts"]}}`} {
Expand All @@ -33,7 +33,7 @@ func TestEnvironmentSetupSharedParsingAndConfidentialSnapshot(t *testing.T) {
}
}
cleared, err := decodeTemplateInput([]byte(`{"env":null,"setup_commands":null,"packages":null}`))
if err != nil || !cleared.Initialization.Empty() || !cleared.SetEnv || !cleared.SetSetup || !cleared.SetPackages {
if err != nil || !cleared.Setup.Empty() || !cleared.SetEnv || !cleared.SetCommands || !cleared.SetPackages {
t.Fatal("nullable replacements", err)
}
}
Expand Down
11 changes: 6 additions & 5 deletions services/core/internal/api/environment_skill_selectors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmentconfig"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmenttemplates"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store"
)

Expand All @@ -23,14 +24,14 @@ func TestSkillReferenceNullableSelectorAdmissionAndTemplateProjection(t *testing
t.Run(test.name, func(t *testing.T) {
skills := `[{"type":"skill_reference","skill_id":"skill-owned"` + test.field + `}]`
template, err := decodeTemplateInput([]byte(`{"skills":` + skills + `}`))
if err != nil || !template.SetSkills || len(template.Initialization.Skills) != 1 {
if err != nil || !template.SetSkills || len(template.Setup.Skills) != 1 {
t.Fatalf("template admission: %+v %v", template, err)
}
want := environmentconfig.Skill{Metadata: environmentconfig.SkillMetadata{Type: "skill_reference", SkillID: "skill-owned", Version: test.selector}}
if !reflect.DeepEqual(template.Initialization.Skills[0], want) {
t.Fatalf("unresolved selector changed: %+v", template.Initialization.Skills[0])
if !reflect.DeepEqual(template.Setup.Skills[0], want) {
t.Fatalf("unresolved selector changed: %+v", template.Setup.Skills[0])
}
public := templateResponse(store.EnvironmentTemplate{Skills: template.Initialization.SkillMetadata()})
public := templateResponse(environmenttemplates.Template{Skills: template.Setup.SkillMetadata()})
var reference map[string]any
if len(public.Skills) != 1 || json.Unmarshal(public.Skills[0], &reference) != nil {
t.Fatalf("template projection: %+v", public.Skills)
Expand All @@ -45,7 +46,7 @@ func TestSkillReferenceNullableSelectorAdmissionAndTemplateProjection(t *testing
t.Fatal(err)
}
input, err := request.validated()
if err != nil || !reflect.DeepEqual(input.initialization.Skills, template.Initialization.Skills) {
if err != nil || !reflect.DeepEqual(input.initialization.Skills, template.Setup.Skills) {
t.Fatalf("Session admission differs from Template: %+v %v", input.initialization.Skills, err)
}
}
Expand Down
11 changes: 5 additions & 6 deletions services/core/internal/api/environment_skills_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ func skillInput(t *testing.T, body string) json.RawMessage {
}

func TestSkillReferenceParsingInheritanceAndReplacement(t *testing.T) {
lookup := &templateLookupStore{network: "enabled", skills: []environmentconfig.Skill{{Metadata: environmentconfig.SkillMetadata{Type: "skill_reference", SkillID: "skill-template", Version: "latest"}}}}
h := templateHandler(t, lookup.ResolveEnvironmentTemplate)
lookup := &templateLookup{network: "enabled", skills: []environmentconfig.Skill{{Metadata: environmentconfig.SkillMetadata{Type: "skill_reference", SkillID: "skill-template", Version: "latest"}}}}
for _, fields := range []string{"", `,"skills":[]`, `,"skills":[{"type":"skill_reference","skill_id":"skill-override","version":"2"}]`} {
var decoded decodedSessionRequest
if err := json.Unmarshal([]byte(`{"agent":{"model":"test"},"environment":{"type":"openai_hosted","environment_template_id":"template"`+fields+`}}`), &decoded); err != nil {
Expand All @@ -47,7 +46,7 @@ func TestSkillReferenceParsingInheritanceAndReplacement(t *testing.T) {
if err != nil || len(intent) == 0 {
t.Fatal("missing unresolved retry intent", err)
}
if err = h.resolveTemplateEnvironment(t.Context(), "tenant", &input); err != nil {
if err = applyTemplateEnvironment(&input, lookup.resolved()); err != nil {
t.Fatal(err)
}
switch fields {
Expand Down Expand Up @@ -91,7 +90,7 @@ func TestInlineSkillsSharedParsingSnapshotAndIntent(t *testing.T) {
skill := skillInput(t, "private-skill-canary")
raw := append(append([]byte(`{"skills":[`), skill...), []byte(`]}`)...)
template, err := decodeTemplateInput(raw)
if err != nil || !template.SetSkills || len(template.Initialization.Skills) != 1 {
if err != nil || !template.SetSkills || len(template.Setup.Skills) != 1 {
t.Fatal("template", err)
}
environment := append([]byte(`{"type":"openai_hosted",`), raw[1:]...)
Expand All @@ -107,7 +106,7 @@ func TestInlineSkillsSharedParsingSnapshotAndIntent(t *testing.T) {
return input
}
inline := decode(`"agent":{"model":"test"}`, environment)
if !bytes.Equal(inline.initialization.Skills[0].Archive, template.Initialization.Skills[0].Archive) {
if !bytes.Equal(inline.initialization.Skills[0].Archive, template.Setup.Skills[0].Archive) {
t.Fatal("inline/template differ")
}
configuration, err := resolve(inline, "tenant", "key", nil)
Expand All @@ -129,7 +128,7 @@ func TestInlineSkillsSharedParsingSnapshotAndIntent(t *testing.T) {
}
for _, clearing := range []string{`{"skills":null}`, `{"skills":[]}`} {
input, err := decodeTemplateInput([]byte(clearing))
if err != nil || !input.SetSkills || !input.Initialization.Empty() {
if err != nil || !input.SetSkills || !input.Setup.Empty() {
t.Fatal("clear", err)
}
}
Expand Down
Loading