Skip to content

Reduce Agent Mode memory from retained provider processes#360

Open
morluto wants to merge 6 commits into
repoprompt:mainfrom
morluto:fix/agent-mode-retained-process-memory
Open

Reduce Agent Mode memory from retained provider processes#360
morluto wants to merge 6 commits into
repoprompt:mainfrom
morluto:fix/agent-mode-retained-process-memory

Conversation

@morluto

@morluto morluto commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR reduces Agent Mode memory pressure from retained provider subprocesses while preserving short-term session reuse.

RepoPrompt intentionally keeps some completed provider runtimes alive so follow-up prompts can reuse the existing session. That is useful, but before this change ACP/OpenCode and Claude-compatible runtimes did not have a bounded idle cleanup equivalent to Codex's idle shutdown.

This PR adds bounded cleanup for those retained runtimes, fixes terminal cleanup ordering, and reduces one smaller Codex model-polling resident process.

Local memory observations

This was motivated by local RepoPrompt CE debug-app investigation of Agent Mode memory pressure.

Observed retained subprocess RSS in that environment:

  • OpenCode/ACP retained processes: often hundreds of MB each, with samples around ~700MB–1GB RSS per opencode acp process.
  • Claude-compatible retained process: one completed CC-compatible probe retained about ~305MB RSS until explicit session deletion.
  • Codex model-polling app-server: about ~60MB RSS while subscribed.

These numbers are workload/provider/environment-dependent, but they show why unbounded retained provider processes can dominate RepoPrompt's memory footprint.

What was happening

ACP / OpenCode

Successful ACP-backed Agent Mode runs intentionally retained their ACPAgentSessionController for follow-up reuse. This keeps the underlying provider process alive after completion.

That is useful briefly, but there was no idle TTL equivalent to Codex's 300s idle shutdown. In local investigation, retained OpenCode ACP processes were the largest memory contributor.

Claude-compatible runtimes

Claude-compatible completed sessions had the same lifecycle shape: the controller/process could remain retained after completion for reuse, but without a Codex-like idle shutdown.

A local Claude-compatible probe confirmed a completed session retained a live native process until the session was explicitly deleted.

Codex model polling

Codex model polling intentionally kept a dedicated non-agent app-server warm while subscribers existed. That avoided restart cost and kept refresh latency low, but also kept an otherwise idle app-server process resident between 60s refreshes.

Since the model snapshot is cached and the client can restart on demand, this PR stops the polling app-server after each refresh while preserving subscriber/cache behavior.

Retained-runtime lifecycle

flowchart TD
    A[Agent run completes] --> B{Terminal publication accepted?}
    B -- no --> C[Do not schedule idle cleanup]
    B -- yes --> D[Keep controller briefly for follow-up reuse]
    D --> E{New run / steering / provider switch / tab close?}
    E -- yes --> F[Cancel idle task or explicit cleanup]
    E -- no, TTL expires --> G{Safe to teardown?}
    G -- no --> H[Keep controller]
    G -- yes --> I[Shutdown retained provider process]
    I --> J[Preserve resume metadata]
Loading

Completed sessions remain reusable during the idle TTL, but ACP/OpenCode and Claude-compatible provider processes are torn down after the TTL if the session is still inactive and safe to clean up.

What changed

  • Added DEBUG-only process memory diagnostics:
    • child PID/RSS attribution for Codex, ACP/OpenCode, and Claude-compatible controllers
    • safer debug JSON serialization
  • Added shared AgentModeIdleShutdownPolicy
    • default remains 300s
    • delay is test-injectable
  • Fixed terminal barrier ordering:
    • rejectable drain/ownership/provider-buffer checks happen before terminal side effects
    • terminal cleanup hooks run only after accepted terminal publication
  • Added bounded idle cleanup for retained ACP/OpenCode controllers.
  • Added bounded idle cleanup for retained Claude-compatible controllers.
  • Preserved resume metadata during cleanup:
    • TabSession.providerSessionID
    • Claude-compatible session refs are captured from NativeAgentRuntimeSessionRef.sessionID
  • Changed Codex model polling to stop its app-server after refresh while keeping cached model data.
  • Added contract-ledger rows for the new executable tests in this PR.

Related issue

Follow-up to #291.

This PR also covers the broader retained-process case beyond unreachable/orphaned ACP sessions: reachable completed ACP/OpenCode and Claude-compatible sessions now have bounded idle cleanup.

Validation

  • make dev-test FILTER=CodexModelPollingServiceTests
  • make dev-test FILTER=AgentModeRunServiceLifecycleTests
  • make dev-test-list
  • make dev-lint
  • make dev-swift-build PRODUCT=RepoPrompt
  • Commit preflight passed for each commit
  • Push preflight passed for the clean PR branch

Ledger note:

  • Added 13 surgical rows to Scripts/Fixtures/test-suite-contract-ledger.tsv for this PR's new executable tests.
  • python3 Scripts/test_suite_optimizer.py verify-ledger --ledger Scripts/Fixtures/test-suite-contract-ledger.tsv was run after the update. It still reports unrelated existing repository drift (missing=50 stale=3); the PR-added rows reduced the missing count from 63 to 50.

Review notes

Commits are split by concern:

  1. chore(agent-mode): add DEBUG process memory diagnostics
  2. fix(codex): release model polling app-server after refresh
  3. fix(agent-mode): bound retained runtime controller lifetimes
  4. test(agent-mode): update lifecycle contract ledger

Suggested review order:

  1. Diagnostics/process attribution helpers.
  2. Codex polling lifecycle; scoped to model discovery.
  3. Runtime lifecycle commit; shared TTL, terminal barrier fix, ACP cleanup, and Claude-compatible cleanup.
  4. Ledger rows for the new executable tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant