carlos / platform Public

host: the agent claims its delegated subtree; the unit file delegates it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell d9e64b61b7758875f65653133c82f2e47c84bdaa parent e79a1f5
2 files changed, +22 −2
  • internal/host/agent.go +10 −1
  • internal/host/units/carlos-edge.service +12 −1
diff --git a/internal/host/agent.go b/internal/host/agent.go
index bc5429a14..2dc19b674 100644
--- a/internal/host/agent.go
+++ b/internal/host/agent.go
@@ -92,6 +92,7 @@ import (
"github.com/carlosframework/platform/internal/activator"
"github.com/carlosframework/platform/internal/appconfig"
"github.com/carlosframework/platform/internal/caps"
+ "github.com/carlosframework/platform/internal/cgroup"
"github.com/carlosframework/platform/internal/domains"
"github.com/carlosframework/platform/internal/edge"
"github.com/carlosframework/platform/internal/lease"
@@ -131,6 +132,13 @@ func RunAgent(args []string) {
// hibernation branch.
cache := edge.NewCache(edge.DefaultCacheMaxEntries, edge.DefaultCacheMaxBodyBytes)
+ // Exec-child accounting: claim the cgroup subtree the unit file
+ // delegates to us (Delegate= in carlos-edge.service). Nil when there is
+ // no delegation — an old unit file, a dev run outside systemd, darwin —
+ // in which case children spawn exactly as before and simply have no
+ // per-instance readings.
+ cgm := cgroup.Setup(log.Printf)
+
cfg, err := activator.ConfigFromEnv()
if err != nil {
log.Fatalf("agent: %v", err)
@@ -148,6 +156,7 @@ func RunAgent(args []string) {
OptDir: release.OptDir(),
DataDir: dd,
Cache: cache,
+ Cgroups: cgm,
},
Unit: &activator.UnitBackend{OptDir: release.OptDir()},
}, reg) // the registry satisfies activator.DueStore directly
@@ -161,7 +170,7 @@ func RunAgent(args []string) {
go statusLoop(reg)
go domainsLoop(reg, peekAcmeCache(args))
go instancesLoop(reg)
- go metricsLoop("")
+ go metricsLoop(cgm.Root())
go configSyncLoop(reg, svc, dd)
go configGenLoop(reg, svc, dd)
go socketAdvisoryLoop(reg, svc)
diff --git a/internal/host/units/carlos-edge.service b/internal/host/units/carlos-edge.service
index aa41e536a..ee57b7f40 100644
--- a/internal/host/units/carlos-edge.service
+++ b/internal/host/units/carlos-edge.service
@@ -38,7 +38,11 @@ ProtectHostname=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
-ProtectControlGroups=yes
+# ProtectControlGroups is deliberately ABSENT: it would mount /sys/fs/cgroup
+# read-only, and Delegate= (below) needs this unit's own subtree writable.
+# The hardening that line bought is retained by ownership instead — plain
+# DAC keeps everything OUTSIDE the chowned subtree read-only to this
+# unprivileged user.
ProtectProc=invisible
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
@@ -53,6 +57,13 @@ SystemCallArchitectures=native
# No MemoryMax: hibernating instances are this unit's children, so a cap here
# would cap the whole set of awake instances at once.
TasksMax=1024
+# Exec-backed instances run as this unit's children. Delegation hands the
+# unit its own cgroup subtree — chowned to User=carlos, unmanaged by systemd
+# below this point — so the agent can give each child its own cgroup for
+# per-instance accounting (docs/superpowers/specs/
+# 2026-08-07-exec-child-accounting-design.md). The subtree still dies with
+# the service: restart semantics are unchanged.
+Delegate=cpu memory io pids
[Install]
WantedBy=multi-user.target