Inspiration

I run several products solo, and my coding agents kept confidently building against decisions that had stopped being true. A policy changes in month three, but the requirements, implementation plans, and agent context that depended on it stay green because nothing is watching the dependency chain.

That failure is not primarily a writing problem. It is an execution problem: how can an agent know whether the intent it is acting on is still valid?

What it does

Gen a Spec holds a project's intent as a graph over an append-only, hash-chained ledger. When new evidence contradicts a decision, it computes the affected dependency closure, records why each node became suspect, and updates readiness.

When an AI agent later asks for permission to build, Gen a Spec can refuse stale execution. The refusal carries a causal certificate: the database-backed chain explaining which decision changed, what it affected, and why execution is blocked.

Approved does not mean approved forever.

The product exposes the same state through a focused judge UI and four Model Context Protocol tools, so both people and agents can inspect readiness, trace impact, propose changes, and compile an execution contract.

It is user driven as well as agent driven, through the same gates. A person signs up, creates a workspace, and pastes a brief in their own words; the model analyses it and stages a proposal together with the one question the brief does not settle; nothing enters the graph until the person confirms it in Review. From then on the intent tree is walkable: every statement shows what rests on it, and a person standing on any decision can Affirm it (supporting evidence), Challenge it (contradicting evidence that immediately recomputes the blast radius through the same path an external webhook uses), or Build on it (new intent, routed back through analysis and human confirmation). Approval and revision both pass through a person; the model only ever proposes.

How we built it

Gen a Spec has two deployed services plus a deliberately separated proposer client, backed by PostgreSQL on Google Cloud:

  • Core — Ruby on Rails 8 on Compute Engine. Owns the ledger, intent graph, invalidation rules, readiness scoring, contract compiler, MCP surface, and background jobs.
  • Proposer client — Python, Google Agent Development Kit, and Gemini 3.5 Flash on Vertex AI. Run by the operator during the current submission window, it reads briefs and proposes graph changes for human confirmation. Its model path has been validated live against Vertex AI; it is not a continuously deployed service.
  • Judge surface — SvelteKit and TypeScript. Turns the graph, impact trace, and readiness state into three focused screens.
  • Data — PostgreSQL 17 on Cloud SQL. Stores the authoritative event ledger and rebuildable projections.

Four architectural properties carry the safety story:

  1. The ledger is the only writable truth. Projections can be dropped and rebuilt by replay.
  2. The authority boundary is a database permission, not a prompt. The model-facing role may propose, while deterministic code authorizes and commits.
  3. Replay never calls a model. Model output enters as an input event, allowing a historical prefix to replay offline and deterministically.
  4. Impact analysis is capped at three hops and discloses the limit. Beyond that, the product says “further impact not analysed” rather than claiming that nothing else is affected.

The deterministic core is tested at 100% line and branch coverage. Property tests exercise ledger integrity, replay without model calls, closure over cycles, the three-hop limit, idempotency, readiness, and stale-token refusal.

Challenges we ran into

The most instructive failures all looked healthy from the outside:

  • The deployed core could read but could not append because its database role was missing the required authority membership. Health checks still returned 200, revealing that deployment verification must exercise a complete write-and-read path.
  • A background job raised an error under a comment that said the queue would retry it, but nothing actually retried it. A test that only asserted the exception also passed.
  • Readiness was not being recomputed, so different surfaces could truthfully report different snapshots of state.
  • A container exceeded its memory allowance on boot, causing irregular latency while requests still returned correct status codes.

We fixed these by testing behavior rather than mere availability: append, propagate, project, refuse, replay, and read the result back.

Accomplishments that we're proud of

We built a dogfood corpus from this repository's own design decisions. The contradiction in the recording pack is a real reversal in the project's deployment architecture, so the system can demonstrate the workflow on the material that created it. This corpus is locally verified; the production judge surface still served the smaller default corpus at the last read-only check and must not be described as dogfooding until a production switch is separately verified.

The core produces a reason-coded, deterministic refusal rather than asking a language model to judge its own output. Its ledger verifies after the full workflow, and the graph can be reconstructed without a network or model call. That replay claim was proven in anger during the build: a projection was genuinely lost to a dropped background job, and the ledger rebuilt it bit for bit.

The extraction pipeline was stress tested with real sloppy input rather than curated briefs: a three-word brief with a typo produced a small, honest proposal plus a question naming exactly what the brief did not say, instead of an invented specification.

What we learned

A test whose subject can be absent may be satisfied by that absence. A refusal-only suite can pass against a component that refuses everything, and two artifacts generated by the same faulty code can agree with each other while both are wrong.

We also learned that verifying only read paths proves that a deployment is switched on, not that the product works. The useful check is the whole invariant: append, propagate, project, compile or refuse, and read back.

What's next

The direction is a distributed intent ledger with a human gate on every entrance. Other systems where decisions actually live, issue trackers such as Jira, code review, CI, policy services, become contributing nodes: their events arrive as evidence or proposed intent over the same connectors the webhook and the UI already use, the model analyses them in context, and nothing becomes belief until a person confirms it. The same gate that today sits between Gemini and the graph sits between every integration and the graph.

Nearer term: resolving an invalidation from the UI (today it is an operator action), attributing each weigh-in to the person who made it, a pannable diagram view of the tree, a user-facing control for when analysis runs, crypto-shred erasure so sensitive payloads can be destroyed while the ledger still verifies, and a stronger evaluation harness for the proposer's analysis passes.

Built With

Share this project:

Updates