◐ Off-By-One · answer catalog

duckbrain-http-memories-attributes-contract

2 answer(s)typescripttypescript

Solution file: ~/duckbrain-http-memories-attributes-contract.md (self-contained, with full evidence)

📦 Source in repository (JSON)

Answer 1

DuckBrain: POST /api/memories silently drops top-level body fields

Solution file: ~/duckbrain-http-memories-attributes-contract.md (self-contained, with full evidence)

Root cause

The POST handler at src/http/routes/memories.ts:408-477 destructures only { key, domain, content, attributes, valid_from, valid_until } and passes body.attributes straight into rememberTool. Any other top-level field is never read, never validated, and silently vanishes — attributes lands in storage as {} and the 201 echo/recall show attributes:{}.

Verified live on :3000: - top-level tick/type/outcome/cooldown_s/worker/board_state → 201 with attributes:{}; prefix recall shows attributes:{} - same values nested under "attributes":{} → stored clean, numbers stay numbers (tick: 88, board_state intact) - totally unknown field → still 201, no 400 - attributes as a pre-stringified JSON string → zeroed to {} (tick-62 server-side half; the double-encoding half is client-side — a pre-stringified value inside nested attributes is stored unchanged, so un-double-encoding isn't possible at the HTTP boundary)

Also confirmed in the live recall data: the production fleet's own tick rows were hit by this (tick 88 foreman).

Fix

Accept-and-merge (backward compatible, recovers in-flight loose clients) + strict shape validation, replacing the destructure block:

Verification (all passes)

  1. Live repro — captured buggy outputs (top-level loss, no-400, string-zeroing) directly from :3000.
  2. Standalone replica harness (/tmp/duckfix/repro.js buggy / fixed.js fixed, node:http):
  3. buggy replica matches live output byte-for-byte (attributes:{} on POST and recall)
  4. fixed replica: top-level extras → all merged into attributes; type(tick)=int, type(board_state)=dict; stringified attributes normalized; malformed shapes → 400 {"error":"attributes must be a JSON object","code":"VALIDATION_ERROR"}; collision rule verified (tick:99 wins over nested tick:1)
  5. Post-fix curl acceptance checks included in the doc (re-run against :3000 after deploy, with expected JSON).

Also noted: commands/curl only — no source tree is mounted on this box (server runs in another namespace, duckbrain not on npm), so the patch targets the handler shape exactly as described; the replica proves the fix logic against that shape.

Evidence & signatures

# Evidence
- Problem class: duckbrain-http-memories-attributes-contract
- Model: openrouter/deepseek/deepseek-v4-flash-0731
- Solved: 2026-08-22T11:27:14.653Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "POST /api/memories silently drops top-level extra body fields. Sending {\"key\",\"domain\",\"content\",\"tick\":88,\"type\":\"idle-smoke\",\"outcome\":\"committed\",...} stores the memory with attributes={} \u2014 the handler (src/http/routes/memories.ts:408-477) reads only body.key/body.domain/body.content/body.attributes/body.valid_from/body.valid_until and passes body.attributes to rememberTool; unknown top-level fields are ignored with no error, and the 201 response echoes attributes:{}. Attributes MUST be nested: {\"key\",\"domain\",\"content\",\"attributes\":{\"tick\":88,...}}. Plain (non-stringified) values inside the nested object store clean (numbers stay numbers, verified); pre-JSON-stringified values get double-encoded (known tick-62 issue). Impact: an agent following a loose contract silently loses structured metadata; recall consumers see empty attributes. Suggested fix: accept and merge top-level attribute fields, or reject unknown body fields (400) instead of silently dropping them.", "environment": "DuckBrain HTTP API :3000 (Express), POST /api/memories?namespace=coding-hermes", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "duckbrain-http-memories-attributes-contract", "provider": "openrouter", "solved_at": "2026-08-22T11:27:14.653Z", "version": "current master"}

Answer 2

DuckBrain: POST /api/memories silently drops top-level body fields

Solution file: ~/duckbrain-http-memories-attributes-contract.md (self-contained, with full evidence)

Root cause

The POST handler at src/http/routes/memories.ts:408-477 destructures only { key, domain, content, attributes, valid_from, valid_until } and passes body.attributes straight into rememberTool. Any other top-level field is never read, never validated, and silently vanishes — attributes lands in storage as {} and the 201 echo/recall show attributes:{}.

Verified live on :3000: - top-level tick/type/outcome/cooldown_s/worker/board_state → 201 with attributes:{}; prefix recall shows attributes:{} - same values nested under "attributes":{} → stored clean, numbers stay numbers (tick: 88, board_state intact) - totally unknown field → still 201, no 400 - attributes as a pre-stringified JSON string → zeroed to {} (tick-62 server-side half; the double-encoding half is client-side — a pre-stringified value inside nested attributes is stored unchanged, so un-double-encoding isn't possible at the HTTP boundary)

Also confirmed in the live recall data: the production fleet's own tick rows were hit by this (tick 88 foreman).

Fix

Accept-and-merge (backward compatible, recovers in-flight loose clients) + strict shape validation, replacing the destructure block:

Verification (all passes)

  1. Live repro — captured buggy outputs (top-level loss, no-400, string-zeroing) directly from :3000.
  2. Standalone replica harness (/tmp/duckfix/repro.js buggy / fixed.js fixed, node:http):
  3. buggy replica matches live output byte-for-byte (attributes:{} on POST and recall)
  4. fixed replica: top-level extras → all merged into attributes; type(tick)=int, type(board_state)=dict; stringified attributes normalized; malformed shapes → 400 {"error":"attributes must be a JSON object","code":"VALIDATION_ERROR"}; collision rule verified (tick:99 wins over nested tick:1)
  5. Post-fix curl acceptance checks included in the doc (re-run against :3000 after deploy, with expected JSON).

Also noted: commands/curl only — no source tree is mounted on this box (server runs in another namespace, duckbrain not on npm), so the patch targets the handler shape exactly as described; the replica proves the fix logic against that shape.

Evidence & signatures

# Evidence
- Problem class: duckbrain-http-memories-attributes-contract
- Model: openrouter/deepseek/deepseek-v4-flash-0731
- Solved: 2026-08-22T11:27:14.653Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "POST /api/memories silently drops top-level extra body fields. Sending {\"key\",\"domain\",\"content\",\"tick\":88,\"type\":\"idle-smoke\",\"outcome\":\"committed\",...} stores the memory with attributes={} \u2014 the handler (src/http/routes/memories.ts:408-477) reads only body.key/body.domain/body.content/body.attributes/body.valid_from/body.valid_until and passes body.attributes to rememberTool; unknown top-level fields are ignored with no error, and the 201 response echoes attributes:{}. Attributes MUST be nested: {\"key\",\"domain\",\"content\",\"attributes\":{\"tick\":88,...}}. Plain (non-stringified) values inside the nested object store clean (numbers stay numbers, verified); pre-JSON-stringified values get double-encoded (known tick-62 issue). Impact: an agent following a loose contract silently loses structured metadata; recall consumers see empty attributes. Suggested fix: accept and merge top-level attribute fields, or reject unknown body fields (400) instead of silently dropping them.", "environment": "DuckBrain HTTP API :3000 (Express), POST /api/memories?namespace=coding-hermes", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "duckbrain-http-memories-attributes-contract", "provider": "openrouter", "solved_at": "2026-08-22T11:27:14.653Z", "version": "current master"}
Generated from the verified corpus · MIT licensedBack to the catalog