How it fits together
GreatBook is five layers. Reading them from the outside in, each one is narrower than the last, and the narrowest is the only one that writes.The layers
The cognitive loop is checkpointed in Postgres, which is what makes the gate durable rather than a
UI convention.
The graph survives a restart while it is paused, and it resumes from the checkpoint rather than
re-running the expensive pipeline from scratch.
The five deployed graphs
Each agent is one LangGraph graph, registered in the deploy manifest, one lane each.The two gates
The accounting path after a document reaches intake has exactly two human decision points, and they are different questions asked of different people. A Typewriter document has an earlier PDF-signing gate as well; that gate approves the document bytes, not their accounting treatment. Native subledger records, approved workpapers, reversals and system events are also valid accounting-event sources; they do not pretend to begin in intake and remain subject to their own canonical service controls and the same single writer. Gate 1, intake. For an uploaded document: “Is this document real and correctly extracted?” For a Typewriter document, which bypasses Clerk extraction, the question is “Does this approved composition belong in the books?” Approving advances the row and launches the Bookkeeper. It moves no money. Gate 2, posting. For that proposed document entry: “Is this journal entry right?” Approval releases the proposal to the ledger service, which remains the only writer. The rule across both document gates is strict: three distinct authenticated people stand behind every document-originated posted journal entry - whoever submitted the document, whoever approved it into the queue, and whoever approved the posting. Not two, and not “two plus a constant”.The approve-to-Bookkeeper handoff is an outbox, not a call.
The intake approval is durable the moment the request returns, and the launch that follows it can
still fail.
The reviewer is told so explicitly rather than being shown an unconditional success for an operation
that may have done nothing, and there are two ways to finish the job: retry one launch, or sweep them.
Two dependency trees, on purpose
The repository carries two Python trees with separate dependency sets, and the separation is a design decision rather than an accident of history.
The reason they are separate is the reason the second one can import the first.
Because the engine’s posting path has a psycopg-and-stdlib closure, the agent runtime can import the real writer in process without dragging an ORM and a web framework along with it.
That is what makes “the agents post through the same engine the API posts through” a fact rather than an aspiration.
There is no second ledger implementation to keep in step, because there is no second implementation.
One migration runner, one chain: alembic, in
backend/migrations/.
Read the live database’s alembic_version before authoring a migration rather than inferring the head
from the versions directory. CI migrates an empty database, so a repository whose head is behind
production stays green while an upgrade against production fails closed.Related
- GreatBook in ChatGPT - the current integration boundary and availability
- The seven guarantees - what each layer is protecting
- Who does what - the people the two gates require
- What is live - the honest status of every component above
- Technical flows and authority - the exact artifacts, stores, identities and refusals from document to journal entry