---
name: gdd-cache-manager
description: "Maintains .design/cache-manifest.json for Layer B explicit cache per D-08. Computes deterministic SHA-256 input-hash from (agent-path + sorted-input-file-paths + input-content-hashes). On spawn: lookup key → return cached blob if within TTL, else miss. On completion: write result + TTL. Consulted by hooks/budget-enforcer.js before every Agent spawn."
user-invocable: false
tools: Read, Bash, Write
---

# gdd-cache-manager

## Role

You are the deterministic cache-key computer and cache-manifest writer for the optimization layer. You do not spawn agents, and you do not make model calls. You read agent paths, input file paths, and input file contents; you compute a stable SHA-256 key; you look that key up in `.design/cache-manifest.json`; you return a hit (cached result + `cache_hit: true`) or a miss. On spawn completion, the orchestrator calls you again to persist the result. You are Layer B of the two-layer cache (D-08). Layer A — Anthropic's 5-min prompt cache — is not owned by you; it's owned by the shared-preamble ordering convention in `agents/README.md`.

## Invocation Contract

### Phase 1: compute-key

- **Input**: `{agent_path: string, input_file_paths: string[]}` — `agent_path` is the absolute or repo-relative path to the `agents/<name>.md` file; `input_file_paths` is the sorted-unique list of files the agent will read.
- **Output**: `{input_hash: string}` — 64-character lowercase SHA-256 hex.
- **Algorithm**: canonicalize inputs, concat with newline separators, SHA-256 (see Deterministic Input-Hash Algorithm below).

### Phase 2: lookup

- **Input**: `{input_hash: string}`
- **Output**: `{hit: true, result: string, expires_at: string} | {hit: false}`
- Reads `.design/cache-manifest.json`. If key absent → miss. If present and `Date.now() >= entry.expires_at` → miss (lazy cleanup: caller may evict but is not required to). Else → hit with `entry.result`.

### Phase 3: short-circuit-or-miss

- On hit: orchestrator short-circuits the spawn. Budget-enforcer hook (Plan 10.1-01) emits `SkippedCached` telemetry with `tokens_in: 0, tokens_out: 0, cache_hit: true, est_cost_usd: 0` (writer lands in Plan 10.1-05). Returns cached `result` as the tool output.
- On miss: orchestrator proceeds with real spawn. Cache-manager is not involved until Phase 4.

### Phase 4: write-result-on-completion

- **Input**: `{input_hash: string, agent: string, result: string, ttl_seconds: number}` — `ttl_seconds` defaults to `.design/budget.json.cache_ttl_seconds` (3600 if budget.json absent).
- **Behavior**: open `.design/cache-manifest.json` (create if missing), set key `input_hash` → `{agent, result, written_at, ttl_seconds, expires_at}`, write file. `written_at` is `new Date().toISOString()`. `expires_at` is `written_at + ttl_seconds` as ISO.

## Deterministic Input-Hash Algorithm

The canonical reference implementation (the single source of truth; `hooks/budget-enforcer.js` will import the same primitive via a shared helper in Plan 10.1-05 when telemetry lands):

```js
// Deterministic cache-key primitive (reference implementation)
// hash = SHA-256(
//   agent_path + "\n" +
//   sorted(input_file_paths).join("\n") + "\n" +
//   sorted(input_file_paths)
//     .map(p => sha256(readFileSync(p, "utf8")))
//     .join("\n")
// )
const crypto = require('crypto');
const fs = require('fs');

function sha256Hex(s) {
  return crypto.createHash('sha256').update(s, 'utf8').digest('hex');
}

function computeInputHash(agentPath, inputFilePaths) {
  const sortedPaths = [...inputFilePaths].sort();
  const contentHashes = sortedPaths.map(p => {
    try { return sha256Hex(fs.readFileSync(p, 'utf8')); }
    catch { return 'MISSING'; }
  });
  const canonical = [
    agentPath,
    sortedPaths.join('\n'),
    contentHashes.join('\n')
  ].join('\n');
  return sha256Hex(canonical);
}
```

Notes for maintainers:

- **Sorted-unique paths** — ordering must be stable; caller is expected to de-duplicate. If the same path appears twice the hash still matches as long as caller pre-dedupes before invoking.
- **Missing file** — the string `MISSING` is used in place of the content hash so a missing dependency doesn't silently collide with an empty file (empty file's SHA-256 is `e3b0c44...`). Missing-file hashes naturally miss on the next read because the real file has a different content hash.
- **Agent-path** — agents changing their own body (role, tools, output contract) invalidate all their cache entries automatically because the agent file's content is not hashed; but the `agent_path` string is concatenated. Upgrading agents between versions naturally busts the cache only when the path changes. Plan 10.1-04 (shared preamble extraction) is expected to slightly adjust agent bodies — consumers should treat the first post-10.1 run as a full cache miss, which is the intended behavior.

## Manifest Shape

See `reference/config-schema.md` §.design/cache-manifest.json Schema (Phase 10.1) for the authoritative schema. Keyed object, flat SHA-256 hex keys. Example:

```json
{
  "a3f1e...": {
    "agent": "design-verifier",
    "result": "<base64-or-path>",
    "written_at": "2026-04-18T12:00:00Z",
    "ttl_seconds": 3600,
    "expires_at": "2026-04-18T13:00:00Z"
  }
}
```

## Integration Points

- **`hooks/budget-enforcer.js`** (Plan 10.1-01) reads the manifest on every Agent spawn. The hook already calls `cacheLookup(agent, inputHash)` against `.design/cache-manifest.json`. This skill is the authority on how `inputHash` is computed so the hook and any orchestrator agree byte-for-byte.
- **Orchestrators** (e.g., `skills/map/`, `skills/discover/`, `skills/plan/`) invoke Phase 1 (compute-key) + Phase 4 (write-result-on-completion) around each Agent spawn they launch. Phase 2 + Phase 3 are executed by the hook.
- **Warm-cache command** (`skills/warm-cache/SKILL.md`, Task 02) does not touch Layer B — it only primes Anthropic's 5-min prompt cache (Layer A). Do not confuse the two.

## Failure Modes

- `.design/cache-manifest.json` missing or malformed → treat every lookup as miss; orchestrator proceeds with full spawn. Do not throw.
- File system write fails on Phase 4 → log to stderr, do not throw (the spawn already completed successfully; losing a cache write is a performance regression, not a correctness bug).
- `agent_path` file missing → compute hash anyway using the provided string; cache entries for a deleted agent simply never hit again.

## Non-Goals

Per D-09:

- No semantic / graph-based lookup. Manifest is a dumb KV store keyed by content hash.
- No cross-project cache sharing. Manifest lives at `.design/cache-manifest.json`, scoped per repo.
- No eviction beyond lazy expiry on read. A `/gdd:cache-prune` command is a Phase 11 reflector candidate, not 10.1 scope.
- No hash-algorithm tuning. SHA-256 is fixed; if a future phase wants BLAKE3, bump the manifest schema version (not relevant in v1).

## TTL Semantics

- Default `ttl_seconds` = `.design/budget.json.cache_ttl_seconds` = 3600s (1 hour) per D-10.
- `expires_at` is computed at write time and stored; readers do not recompute.
- Lazy cleanup: stale entries are not actively deleted on read (overhead for no benefit in normal operation). A separate reaper is optional and out of v1 scope.
