diff --git a/setup/service-renderers.ts b/setup/service-renderers.ts index 85287b4..99e609f 100644 --- a/setup/service-renderers.ts +++ b/setup/service-renderers.ts @@ -113,6 +113,15 @@ WorkingDirectory=${projectRoot} Restart=always RestartSec=5 RestartPreventExitStatus=${STARTUP_PRECONDITION_EXIT_CODE} +# Per-turn agent runners do heavy file I/O (git clone, builds, video/ffmpeg +# scratch) inside this service cgroup. The kernel keeps those reads as +# reclaimable page cache, which makes systemd's MemoryCurrent climb to several +# GB even though real (anon) usage stays a few hundred MB. MemoryHigh applies +# gentle reclaim pressure so that cache is dropped before the number balloons, +# while MemoryMax stays unset (infinity) so a legitimate burst is never +# OOM-killed — it is throttled at worst. +MemoryAccounting=yes +MemoryHigh=3G ${envLines.join('\n')} StandardOutput=append:${projectRoot}/logs/${def.logName}.log StandardError=append:${projectRoot}/logs/${def.logName}.error.log diff --git a/setup/service.test.ts b/setup/service.test.ts index 61de7cd..a940465 100644 --- a/setup/service.test.ts +++ b/setup/service.test.ts @@ -111,6 +111,20 @@ describe('systemd unit generation', () => { expect(unit).toContain('RestartPreventExitStatus=78'); }); + it('bounds reclaimable page cache with a soft memory limit', () => { + const unit = buildSystemdUnit( + baseServiceDef, + '/home/user/ejclaw', + '/usr/bin/node', + '/home/user', + false, + ); + expect(unit).toContain('MemoryAccounting=yes'); + expect(unit).toContain('MemoryHigh=3G'); + // MemoryMax must stay unset so a real burst is throttled, never OOM-killed. + expect(unit).not.toContain('MemoryMax='); + }); + it('sets correct ExecStart', () => { const unit = buildSystemdUnit( baseServiceDef,