akw-factory-floorsfloors.akwlabs.dev · ga

The garage and reception

Where an idea enters, how it gets an immutable name, and what it costs — sketched, not built.

The flow

flowchart LR
  U([a person has an idea]) -->|one append| G[floor-garage<br/>garage.jsonl]
  G -->|pulled| R[floor-reception<br/>its own mailroom]
  R -->|routed| F[floor-&lt;role&gt;<br/>floor-mailroom.jsonl]
  F -.->|run observed, later| L[floor-garage<br/>ledger.jsonl<br/><i>not built</i>]
  L -.->|derived| G

Four logs. All append-only. All joined by one key.

One key: the prompt is its own name

id = sha256(prompt)[0:16]

The id is the content. That gets three properties for free rather than by discipline:

A fixed-width hex string. A Redis field, a Postgres primary key, a filename. It cannot drift from its value, because it is derived from it. Edit the prompt and you have a different idea, which is the honest answer. The same idea written twice collides. No "is this already filed" step.

floor-garage — the only new shape

floor-garage/garage.jsonl, one row per idea. Minimal on purpose: entering an idea should cost one append and no decisions.

{"id":"3205b4b7e770ea84","ts":"2026-09-01T21:03:11.402Z","kind":"request","state":"parked","prompt":"Adding an idea should cost one command...","prompt_tokens_est":36,"agent_tokens":null}
Field Grade What
id measured sha256(prompt)[0:16]
ts measured when it was parked
kind declared idea, issue, or request
state declared parked until reception pulls it
prompt the text, verbatim
prompt_tokens_est inferred ceil(chars / 4). A heuristic, named as one
agent_tokens null, never 0

floor-reception — no new shape at all

Reception needs no new file. Its mailroom already is the routing log, and the event vocabulary already covers it:

Event Means
delivered an idea arrived from the garage
dispatched it was routed to a role floor

The body line carries the id and the destination. This is the cheapest correct answer: reception is a queue, a queue is an append-only log, and every floor already has one.

What it costs — the part you left open

You cannot know an idea's agent cost while writing the prompt. You can know it afterwards, and the mechanism already exists.

Claude Code writes one JSONL transcript per session under ~/.claude/projects/<slug>/<session-id>.jsonl. Every assistant row carries message.usage. Measured 2026-09-01: 249 of 249 assistant rows in one session, with input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens and server_tool_use.

So cost is not a field somebody fills in. It is a third log, harvested:

{"id":"3205b4b7e770ea84","ts":"...","floor":"floor-data","session":"df316340","input_tokens":18402,"output_tokens":2911,"cache_read_input_tokens":911204,"tool_calls":37}

An idea's cost then becomes derived — the mean or the worst case over its ledger rows — and it improves as runs accumulate. Zero rows means unknown, which is honest and is the same rule the rest of this repo follows: never write a number you did not measure.

"JSONL, or something more performant?"

Keep JSONL. The performance tier already exists and is a different layer.

This repo's standing rule is that the JSONL is the record and every database is a rebuildable cache of it. src/load/pglite.ts and src/load/ioredis.ts already do that for the manifest and the mailrooms; the garage joins them as two more tables and one more key prefix. If reading the garage ever becomes slow, that is an argument for loading it, not for changing the file.

A content-addressed id makes both loaders trivial: primary key in Postgres, hash field in Redis, no surrogate key and no sequence.

What did not change

Adding two floors required no code change at all. npm run gen:mailroom gave both a mailroom, npm run gen:manifest picked them up, and every gate passed. The 20 role floors, the generators, the loaders, the Ink view and the hooks are untouched.

That is the abstraction working: a floor is a directory with a mailroom, and the manifest is a cache of whatever directories exist.

The one place it strains

The manifest now says this, and it is wrong in a way worth keeping visible:

{"slug":"floor-garage","role":"garage","file_count":1,"populated":true,"mailroom_rows":1}

garage is not a role, and populated: true here means "holds a backlog", not "carries plugin content" — so the 11-populated / 9-shell split that the split exists to express is now diluted. The fix is a kind column on ManifestRow, role or infrastructure.

It is filed in the garage rather than applied, because it changes src/types.ts, the generated zod, both loaders, the Ink view and the tests — which is more than a sketch should spend.

The backlog is in the garage

Six rows, and they are the open questions about the garage itself:

Kind In one line
issue harvest agent cost from transcripts into ledger.jsonl
request ManifestRow needs a kind column
request garage.jsonl has no validator and nothing grades it
request npm run garage -- add "<prompt>" — one command, no hand-written JSON
request reception should record why an idea was routed
issue nothing expires an idea
python3 -c "import json;[print(r['kind'], r['id'], r['prompt'][:60]) for r in map(json.loads, open('floor-garage/garage.jsonl'))]"