SC-2 Render-to-disk pipeline (agent-env-sync renderForInstance) #624

Closed
opened 2026-05-01 10:33:08 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As a platform engineer, I want the per-agent env directory to mirror the agent-config resolver's output, so that DB writes propagate to claude-code (which reads settings.json / .claude.json from disk) on boot, on every config change, and just-in-time before each dispatch.

Acceptance criteria

Renderer

  • New module apps/server/src/infrastructure/agent-env-sync/render-for-instance.ts exports renderForInstance(agent: ResolvedAgent): Promise<void>.
  • Resolves every kind via the SC-1 resolver. Substitutes ${SECRET:NAME} placeholders by reading from the secret table (decrypts; logs each read into secret_access_log with the supplied accessor + reason).
  • Writes:
    • settings.json::enabledPlugins from resolvePlugins(agent).
    • settings.json::extraKnownMarketplaces from resolveMarketplaces(agent).
    • .claude.json::mcpServers from resolveMcpServers(agent) with secrets baked in.
    • <env-dir>/system-prompt.md from resolveSystemPrompt(agent).
  • Skills are NOT written to disk. The dispatch path queries the resolver at task-build time (SC-3 swap).
  • Idempotent: hash the rendered file content; skip writes when the hash matches the file on disk. No spurious container restarts.

Triggers

  • Service boot: enumerate agents table, renderForInstance each.
  • DB write to any per-kind table (skill, system_prompt, plugin_binding, plugin_marketplace, mcp_server, agent_type_config): enqueue the affected instances. Agent-type writes affect every instance of the type; global writes affect every instance.
  • Pre-dispatch: re-render the target instance just before the task is constructed.

Errors

  • Missing-secret error: render fails with MissingSecretError(name). Pre-dispatch path surfaces as a clear task_failed SSE event with the secret name. Boot path logs and skips (do not crash the service — let the operator fix the secret in the dashboard).

Tests

  • Unit: render produces the expected files for a fixture agent + fixture rows.
  • Unit: idempotence — second render with no DB changes makes zero writes.
  • Unit: secret substitution rewrites ${SECRET:FOO} → ciphertext-decrypted value; logs the access.
  • Integration: changing one plugin_binding row triggers exactly one re-render for the affected instance.

Out of scope

  • Replacing existing agents.json consumers — handled in SC-3 / SC-4 / SC-5.
  • HTTP routes / UI — SC-7+.

References

  • specs/agent-config-customization.md §Render-to-disk and §Story SC-2
  • apps/server/src/infrastructure/agent-env-sync/ — existing materialiser (today copies plugins from agents.json into settings.json)
  • apps/server/src/infrastructure/container/container-reconcile.ts — drift detector that also reads the rendered files
  • Depends on SC-1 (schema + resolver) and SC-6 (encryption primitives) before MCP env substitution can land cleanly.
## User story As a platform engineer, I want the per-agent env directory to mirror the agent-config resolver's output, so that DB writes propagate to claude-code (which reads `settings.json` / `.claude.json` from disk) on boot, on every config change, and just-in-time before each dispatch. ## Acceptance criteria ### Renderer - [ ] New module `apps/server/src/infrastructure/agent-env-sync/render-for-instance.ts` exports `renderForInstance(agent: ResolvedAgent): Promise<void>`. - [ ] Resolves every kind via the SC-1 resolver. Substitutes `${SECRET:NAME}` placeholders by reading from the `secret` table (decrypts; logs each read into `secret_access_log` with the supplied accessor + reason). - [ ] Writes: - `settings.json::enabledPlugins` from `resolvePlugins(agent)`. - `settings.json::extraKnownMarketplaces` from `resolveMarketplaces(agent)`. - `.claude.json::mcpServers` from `resolveMcpServers(agent)` with secrets baked in. - `<env-dir>/system-prompt.md` from `resolveSystemPrompt(agent)`. - [ ] Skills are NOT written to disk. The dispatch path queries the resolver at task-build time (SC-3 swap). - [ ] Idempotent: hash the rendered file content; skip writes when the hash matches the file on disk. No spurious container restarts. ### Triggers - [ ] Service boot: enumerate `agents` table, `renderForInstance` each. - [ ] DB write to any per-kind table (`skill`, `system_prompt`, `plugin_binding`, `plugin_marketplace`, `mcp_server`, `agent_type_config`): enqueue the affected instances. Agent-type writes affect every instance of the type; global writes affect every instance. - [ ] Pre-dispatch: re-render the target instance just before the task is constructed. ### Errors - [ ] Missing-secret error: render fails with `MissingSecretError(name)`. Pre-dispatch path surfaces as a clear `task_failed` SSE event with the secret name. Boot path logs and skips (do not crash the service — let the operator fix the secret in the dashboard). ### Tests - [ ] Unit: render produces the expected files for a fixture agent + fixture rows. - [ ] Unit: idempotence — second render with no DB changes makes zero writes. - [ ] Unit: secret substitution rewrites `${SECRET:FOO}` → ciphertext-decrypted value; logs the access. - [ ] Integration: changing one `plugin_binding` row triggers exactly one re-render for the affected instance. ## Out of scope - Replacing existing `agents.json` consumers — handled in SC-3 / SC-4 / SC-5. - HTTP routes / UI — SC-7+. ## References - `specs/agent-config-customization.md` §Render-to-disk and §Story SC-2 - `apps/server/src/infrastructure/agent-env-sync/` — existing materialiser (today copies plugins from agents.json into settings.json) - `apps/server/src/infrastructure/container/container-reconcile.ts` — drift detector that also reads the rendered files - Depends on **SC-1** (schema + resolver) and **SC-6** (encryption primitives) before MCP env substitution can land cleanly.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#624
No description provided.