fix(concierge): provision kind=platform with the platform-agent template (#30/#2970) #3029

Merged
core-devops merged 1 commits from fix/concierge-identity-template-platform-agent into main 2026-06-18 03:52:50 +00:00
3 changed files with 50 additions and 4 deletions
@@ -0,0 +1,27 @@
package handlers
import (
"testing"
"git.moleculesai.app/molecule-ai/molecule-core/workspace-server/internal/models"
)
// RFC §5.7 / #30: a kind='platform' concierge with no explicit template must
// resolve to the platform-agent template (its identity), not the generic
// claude-code-default config.
func TestConciergeTemplateOrDefault(t *testing.T) {
cases := []struct {
name, kind, template, want string
}{
{"platform empty -> platform-agent", models.KindPlatform, "", "platform-agent"},
{"platform blank -> platform-agent", models.KindPlatform, " ", "platform-agent"},
{"platform explicit kept", models.KindPlatform, "custom", "custom"},
{"workspace empty stays empty", "workspace", "", ""},
{"workspace seo-agent kept", "workspace", "seo-agent", "seo-agent"},
}
for _, c := range cases {
if got := conciergeTemplateOrDefault(c.kind, c.template); got != c.want {
t.Errorf("%s: conciergeTemplateOrDefault(%q,%q)=%q want %q", c.name, c.kind, c.template, got, c.want)
}
}
}
@@ -587,9 +587,9 @@ func installPlatformAgent(ctx context.Context, database *sql.DB, platformID, nam
// provisioning state. The integration tests use unique names per fixture
// to avoid cross-test collision (CR-A RC 10610).
if _, err := tx.ExecContext(ctx, `
INSERT INTO workspaces (id, name, kind, tier, status, runtime, parent_id)
VALUES ($1, $2, 'platform', 0, 'offline', 'claude-code', NULL)
ON CONFLICT (id) DO UPDATE SET kind = 'platform', runtime = 'claude-code', parent_id = NULL
INSERT INTO workspaces (id, name, kind, tier, status, runtime, parent_id, template)
VALUES ($1, $2, 'platform', 0, 'offline', 'claude-code', NULL, 'platform-agent')
ON CONFLICT (id) DO UPDATE SET kind = 'platform', runtime = 'claude-code', parent_id = NULL, template = 'platform-agent'
`, platformID, name); err != nil {
return fmt.Errorf("upsert platform agent: %w", err)
}
@@ -286,6 +286,25 @@ func workspaceMemoryNamespace(workspaceID string) string {
return fmt.Sprintf("workspace:%s", workspaceID)
}
// conciergeTemplateOrDefault forces the platform-agent template for a
// kind='platform' concierge when no explicit template is set. RFC §5.7: the
// concierge identity (config.yaml + prompts/concierge.md + mcp_servers.yaml) is
// delivered "like any other runtime template" via the platform-agent template
// entry. But the platform-agent workspace row was upserted with no `template`
// (platform_agent.go installPlatformAgent), so payload.Template was empty and
// the identity resolved to the GENERIC claude-code-default config — the
// concierge booted online but with no persona ("doesn't know it's the platform
// agent", #30/#2970). Forcing "platform-agent" here makes the asset fetcher pull
// the concierge identity for every concierge provision/restart, new or existing,
// without depending on the row's template column being backfilled. An explicit
// template (set by a future caller) still wins.
func conciergeTemplateOrDefault(kind, template string) string {
if kind == models.KindPlatform && strings.TrimSpace(template) == "" {
return "platform-agent"
}
return template
}
func (h *WorkspaceHandler) buildProvisionerConfig(
ctx context.Context,
workspaceID, templatePath string,
@@ -416,7 +435,7 @@ func (h *WorkspaceHandler) buildProvisionerConfig(
// not duplicated across first-provision + restart paths.
// nil fetcher = "no fetcher wired" (self-host default;
// falls through to the local TemplatePath path).
TemplateIdentity: templateIdentityForTemplateOrRuntime(payload.Template, payload.Runtime),
TemplateIdentity: templateIdentityForTemplateOrRuntime(conciergeTemplateOrDefault(kind, payload.Template), payload.Runtime),
TemplateAssetFetcher: h.giteaTemplateFetcher,
}
}