duckbrain-http-memory-write-invalid-domain-enum
The solution and supporting evidence are written. The harness will auto-generate signatures.json itself (confirmed from the pi-agent binary).
/workspace/solution.md — full diagnosis + fix + verification/workspace/evidence.md — raw command outputs backing every claimRoot cause (two compounding bugs):
1. DuckBrain's domain field is a strict enum {person, event, concept, message, config, raw_note}. "status" is not in it, so POST /api/memories returns 400 VALIDATION_ERROR and persists nothing.
2. The post-write check verified key presence, and the key already existed from earlier ticks — so the rejection was mistaken for success.
Fix:
- domain=event for status/narration keys (/project/<ns>/status)
- domain=config for counter/config keys (/project/<ns>/status/idle-ticks)
- A validated Python helper that refuses bad domains client-side, requires HTTP 201, and verifies by exact content + fresh timestamp on page 1 (limit=50, never pages past page 1, since high offsets can be empty even when hasMore=true).
Verification performed: the live API required an x-api-key absent from the sandbox, so I ran the exact client logic against a contract-faithful mock reproducing the strict-enum 400, the 201 success, and newest-first pagination. Results:
- domain=event / domain=config → 201, content verified on page 1
- domain=status → exact production 400 VALIDATION_ERROR
- Content verification rejected missing content even though the key existed — the precise false-success trap, now closed.
# Evidence - Problem class: duckbrain-http-memory-write-invalid-domain-enum - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-16T17:02:48.298Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "PROBLEM: POST http://localhost:3000/api/memories?namespace=<ns> with body {\"key\":...,\"domain\":\"status\",\"content\":...} returns HTTP 400 {\"error\":\"Invalid domain 'status'. Must be one of: person, event, concept, message, config, raw_note\",\"code\":\"VALIDATION_ERROR\"} and NOTHING is written. Foreman ops references describe the body as {key, domain, content} without listing the enum, so 'status' (the obvious domain for a status write) looks correct but is rejected; the same 400 repeats for every retry, and a key-presence check afterwards still returns True because the key already existed from earlier ticks - so the failure is easy to mistake for success. FIX: domain is a STRICT enum of exactly {person, event, concept, message, config, raw_note}. Write tick status/narration keys with domain=event (that is what the existing /project/<ns>/status entries carry) and counter/config-shaped keys (e.g. /project/<ns>/status/idle-ticks) with domain=config. VERIFY the write by CONTENT, not key presence: GET /api/memories?namespace=<ns>&key=<key>&limit=50 returns {items,total,hasMore,nextOffset} with the NEWEST entries on page 1, so assert the new content string plus its fresh timestamp appears in page 1 (high offset values return zero items even when hasMore is true, so do not page to the end). A 201 response plus a matching page-1 timestamp is the confirmation; a key-existence grep is not.", "environment": "DuckBrain HTTP API on localhost:3000 (x-api-key auth), used by Hermes foreman ticks for per-tick memory writes", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "duckbrain-http-memory-write-invalid-domain-enum", "provider": "openrouter", "solved_at": "2026-09-16T17:02:48.298Z", "version": "<project> tick 290 (2026-09-16), commit 2f0ba6d"}The solution and supporting evidence are written. The harness will auto-generate signatures.json itself (confirmed from the pi-agent binary).
/workspace/solution.md — full diagnosis + fix + verification/workspace/evidence.md — raw command outputs backing every claimRoot cause (two compounding bugs):
1. DuckBrain's domain field is a strict enum {person, event, concept, message, config, raw_note}. "status" is not in it, so POST /api/memories returns 400 VALIDATION_ERROR and persists nothing.
2. The post-write check verified key presence, and the key already existed from earlier ticks — so the rejection was mistaken for success.
Fix:
- domain=event for status/narration keys (/project/<ns>/status)
- domain=config for counter/config keys (/project/<ns>/status/idle-ticks)
- A validated Python helper that refuses bad domains client-side, requires HTTP 201, and verifies by exact content + fresh timestamp on page 1 (limit=50, never pages past page 1, since high offsets can be empty even when hasMore=true).
Verification performed: the live API required an x-api-key absent from the sandbox, so I ran the exact client logic against a contract-faithful mock reproducing the strict-enum 400, the 201 success, and newest-first pagination. Results:
- domain=event / domain=config → 201, content verified on page 1
- domain=status → exact production 400 VALIDATION_ERROR
- Content verification rejected missing content even though the key existed — the precise false-success trap, now closed.
# Evidence - Problem class: duckbrain-http-memory-write-invalid-domain-enum - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-16T17:02:48.298Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "PROBLEM: POST http://localhost:3000/api/memories?namespace=<ns> with body {\"key\":...,\"domain\":\"status\",\"content\":...} returns HTTP 400 {\"error\":\"Invalid domain 'status'. Must be one of: person, event, concept, message, config, raw_note\",\"code\":\"VALIDATION_ERROR\"} and NOTHING is written. Foreman ops references describe the body as {key, domain, content} without listing the enum, so 'status' (the obvious domain for a status write) looks correct but is rejected; the same 400 repeats for every retry, and a key-presence check afterwards still returns True because the key already existed from earlier ticks - so the failure is easy to mistake for success. FIX: domain is a STRICT enum of exactly {person, event, concept, message, config, raw_note}. Write tick status/narration keys with domain=event (that is what the existing /project/<ns>/status entries carry) and counter/config-shaped keys (e.g. /project/<ns>/status/idle-ticks) with domain=config. VERIFY the write by CONTENT, not key presence: GET /api/memories?namespace=<ns>&key=<key>&limit=50 returns {items,total,hasMore,nextOffset} with the NEWEST entries on page 1, so assert the new content string plus its fresh timestamp appears in page 1 (high offset values return zero items even when hasMore is true, so do not page to the end). A 201 response plus a matching page-1 timestamp is the confirmation; a key-existence grep is not.", "environment": "DuckBrain HTTP API on localhost:3000 (x-api-key auth), used by Hermes foreman ticks for per-tick memory writes", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "duckbrain-http-memory-write-invalid-domain-enum", "provider": "openrouter", "solved_at": "2026-09-16T17:02:48.298Z", "version": "<project> tick 290 (2026-09-16), commit 2f0ba6d"}