The Foundation Charter
Choose dependable foundations before delegating substantial work to machines.
The New Programming set out the division of labour: the machine increasingly produces the code, while I define the behaviour, organise the work, and judge the result. This chapter answers the first practical question that follows.
If the machine is going to do the coding, what should I decide before the work begins?
Ask a capable model to start a new project with no constraints and watch what happens. It will recommend several databases, compare frameworks, sketch alternative architectures, and generate configuration for all of them — before anyone has properly understood the problem. Left unconstrained, it will keep doing this. Each new task becomes an occasion to revisit settled questions, to introduce an inconsistent pattern, or to add a dependency that solves one small problem while creating three larger ones.
The answer is unglamorous: understand the problem, establish the broad solution, and choose foundations I understand well enough to supervise.
Fluency is not judgement. A machine can propose frameworks and compare databases in seconds. I still need a starting point I know well enough to direct, to recognise when the machine has drifted, and to diagnose failure when — not if — it occurs.
I am not trying to constrain what the machine can achieve. I am removing decisions that do not need to be reconsidered with every task. Every architectural decision I settle in advance is one less decision the machine needs to make repeatedly. I offer that as a working principle from experience, not as a laboratory finding: settled foundations narrow the machine’s freedom where narrowing helps, so its effort goes into the behaviour that actually matters.
This chapter presents my own starting point as an example, not a compulsory stack. I generally start with PostgreSQL, Python, ZeroMQ, HTML, and YAML. That combination forms a workable baseline for many projects. It is not the only valid set of choices, and it does not remove the need to think.
Know the tools you depend upon
A good craftsperson knows their tools. They know what each tool does, what it does well, where it fails, and how to recognise a problem.
Machine coding makes that knowledge more valuable, not less, because I may no longer be writing most of the implementation by hand.
When something goes wrong — and in any real system, something will — I need enough understanding to distinguish a problem in the application logic from one in the database, the messaging layer, the configuration, or the testing environment. A machine can help with each of those diagnoses, but only if I can frame the question and evaluate the answer.
I do not need to know how to build every tool. I do need to know how to use the tools I depend upon — well enough to guide the machine, to understand its explanations, and to notice when its proposal is idiomatic but wrong.
Start with the problem, not the framework
Before selecting technologies, I establish the shape of the work:
- What problem is being solved, and for whom?
- What is the general solution in broad architectural terms?
- What information must be stored, and for how long?
- What operations might take a long time and should not block the user?
- What components must communicate, and how promptly?
- What will users interact with?
- What behaviour must be verified before a change is accepted?
- What operational constraints matter: backup, recovery, audit, cost, deployment?
The charter grows out of those answers. A small internal tool with one user and modest data has different needs from a multi-user system with long-running operations and audit requirements. Writing down which situation I believe I am in gives later decisions — mine or the machine’s — something to refer back to. It also keeps me from recommending an elaborate architecture for a trivial application, or collecting technologies simply because they are available.
I generally start with PostgreSQL
My preferred general-purpose database is PostgreSQL: a mature relational database with transactional integrity, well-understood indexing and querying, and established backup and recovery tooling. Its documentation is extensive, and its behaviour with Python and other major languages is well trodden. When something fails, there is usually a known procedure, a log message with a known meaning, and a community explanation that matches the observed symptoms. That confidence matters when a machine has just produced several hundred lines of database code I did not write.
Two capabilities of that base deserve attention, because they let one database cover work that might otherwise require additional services.
First, pgvector adds vector similarity search to PostgreSQL, so embeddings can live alongside relational data in ordinary tables. It supports exact and approximate nearest-neighbour search with common distance measures, and it preserves the transactional properties of the surrounding database, including the write-ahead logging used for replication and point-in-time recovery.
Second, PostgreSQL provides built-in full-text search, documented in Chapter 12 of the manual, with parsing, dictionaries, ranking, and index support for textual documents.
The appeal is consolidation. With relational storage, vector similarity, and full-text search available within one established database, I can address many common application requirements without immediately introducing additional infrastructure. Fewer independent components can mean fewer integration boundaries, fewer credentials to manage, fewer deployment steps, and fewer operational failure modes for the machine to misunderstand. Keyword and vector retrieval can also be combined — hybrid queries with re-ranking, for example — rather than treated as identical or mutually exclusive.
PostgreSQL is not the right answer everywhere, and installing an extension does not by itself produce good search. Retrieval quality still depends on indexing and ranking decisions, and high-throughput time series, demanding graph traversals, embedded use, or managed-service constraints can justify other choices. My rule is to choose the boring option deliberately and to ask for a reason before departing from it.
Python is my default, not my dogma
For this book, assume Python is the primary application language.
I choose Python for concrete reasons. It has longstanding support in machine learning and data tooling, mature testing and web frameworks, broad documentation, and readable syntax that makes review comparatively straightforward. Common patterns, project layouts, and test conventions are widely shared, which helps both humans and coding tools find their bearings. The Python documentation is stable and easy to point both parties towards.
The case for any particular language is becoming less decisive now that machines perform most implementation work, and I do not insist on Python for everyone. Someone experienced with Go, Rust, Java, C#, JavaScript, or another language does not need to switch in order to practise machine coding.
My principle is narrower: choose a language appropriate to the project that you can navigate when problems arise. Familiarity matters for debugging, for reviewing architectural choices, for interpreting error traces, and for maintaining the project after the machine has produced a large volume of code. Choose what you can supervise.
Give long-running work somewhere to go
My preference for ZeroMQ is somewhat unconventional, so I want to explain it carefully — and to separate two problems that are easy to conflate.
The first problem is background work. Many applications need to perform operations that take time: calling an external model, processing a document, rebuilding an index, running a batch of checks. If that work blocks the interactive path, the user waits while the application appears unresponsive, and a single slow dependency can stall the whole interface. The remedy is to decouple the request for work from the process that performs it: record or dispatch the request, let a worker carry it out separately, keep the application responsive, and show the result when it arrives.
The second problem is notification. Once work has completed or state has changed, interested components — a dashboard, a progress view, another service — need to discover that something happened. That is a different job from executing the work itself.
ZeroMQ is a mature messaging library that can support communication between threads, processes, and services in both kinds of design. The ØMQ guide covers its patterns from basic exchanges through reliable request-reply, and treats reliability as a design problem rather than a promise. ZeroMQ supplies the messaging primitives; it does not itself create workers, schedule jobs, or retain state durably. Message loss on restart, reconnect behaviour, retries, idempotency of handlers, and persistence of pending work all need explicit answers.
ALIVE shows one way to divide these responsibilities, as described in its architecture notes. PostgreSQL retains events in a transactional event log. An outbox publisher emits notifications over ZeroMQ PUB/SUB. A subscriber helps wake the dashboard, and the dashboard can reconstruct anything it missed by re-reading the persisted history. Delivery of any single notification is not treated as durable; durability lives in the database.
The lesson generalises cleanly:
PostgreSQL preserves what happened. ZeroMQ helps interested components discover that something happened. The two mechanisms serve different purposes.
A simple request-response application with fast handlers may need neither workers nor notifications. I introduce decoupling when long-running work, background processing, or independent components justify it — and I record that decision in the charter, including which problem each mechanism is meant to solve.
Prefer familiar presentation technologies
For many applications I prefer an HTML-based web interface: HTML, CSS, and JavaScript in the browser, communicating with backend services over familiar APIs.
Web interfaces run in every major browser, benefit from established development and debugging tools, and draw on extensive libraries and examples. Standard presentation reduces the amount of unfamiliar technology a machine needs to manage: forms, tables, navigation, and authentication follow patterns that both people and models have seen many times. The same stack is convenient even in local-first applications, where a browser view over a local backend avoids much of the friction of native packaging.
I do not mandate a particular JavaScript framework, and native desktop applications remain valid where appropriate. My guidance is to prefer the familiar default and to justify departures from it.
Keep configuration explicit
I use YAML for human-readable configuration: application and development settings, workflow definitions, declarative environment descriptions, and other material that should be version-controlled and reviewed as data rather than buried in implementation code.
A versioned workflow that declares a sequence of operations — what runs, in what order, with which parameters — is a good example. Its behaviour can be inspected without reading the implementation, which makes the intended behaviour available to both the human reviewer and the machine worker.
YAML needs care. Types, indentation, parsing subtleties, and schema drift cause real defects, so I validate configuration against a schema, keep the boundary between configuration and executable behaviour explicit, and never put secrets in committed configuration files. Where YAML proves brittle, TOML, JSON, or ordinary code configuration may serve better. The aim is explicit, reviewable configuration — not loyalty to a file format.
Why settled foundations help
Brought together, these choices express the thesis I stated at the outset. Familiar, mature technologies reduce unnecessary uncertainty, and each settled decision removes a class of choices the machine would otherwise remake on every task.
Widely adopted tools come with documentation, examples, conventions, testing practices, and communities. Coding systems can put those established patterns to productive use when the repository follows them. Conventional choices also give me more to stand on: it is easier to define requirements, to set architectural conventions, to spot an unusual proposal, to diagnose an error, to compare alternatives, to ask for an independent review, and to replace one implementation without redesigning the whole system.
I think of the charter as leverage. The hours spent choosing and writing down foundations pay back across dozens of delegated tasks, because each task starts from the same ground instead of renegotiating it.
Testing needs supervision too
Testing belongs in the charter from the beginning, even though verification gets fuller treatment later in the book.
A machine that produces code rapidly also produces tests rapidly, and I have watched the consequences first-hand: the suite grows quickly, slows down, accumulates duplication, and starts consuming more attention than the source code. At that point I am supervising the testing system rather than the product — a strange position to find oneself in, and one worth preventing.
I find it helps to distinguish the kinds of tests taking up time. Useful coverage of consequential behaviour earns its place. Duplicate or low-value tests restate what neighbouring tests already prove. Fragile tests fail on irrelevant changes and teach everyone to ignore red builds. Expensive tests — slow suites, live model calls, external services — are sometimes necessary, particularly at integration and system boundaries, but they should be identified as such rather than mixed silently into the routine loop.
My working arrangement is two paths. A fast routine suite runs often and stays
quick enough to guide ordinary development. A separate comprehensive path —
integration tests, end-to-end behaviour, live-provider checks — runs on a
stated schedule and guards the properties the fast loop cannot reach. Measured
execution times keep this honest; pytest’s --durations reporting (see
how to invoke pytest)
makes slow tests visible rather than merely felt.
One caution from our experience: isolated tests have passed while the integrated functionality remained unavailable or incorrectly wired. A green suite is necessary evidence, but it does not always prove the software works. The charter’s job is to make sure testing starts early and stays itself under supervision; later chapters take up independent verification and quality gates.
A worked example
A table of headings becomes useful only when it is filled in. Here is a compact charter for a clearly illustrative project — not a description of ALIVE, but the kind of document I would hand to an agent before delegating work on something like it.
Suppose I am building a local-first assistant that ingests project documents, extracts passages, and answers questions with citations. The users are a small team working with sensitive material that must stay on their machines. The broad architecture is a Python service with a local database, background ingestion workers, and a browser interface.
| Decision | Choice for this project |
|---|---|
| Problem | Team members cannot find or cite passages across a growing set of local documents |
| General solution | Local Python service; ingest and index documents in the background; answer questions through a browser view with citations |
| Primary language | Python, because I can debug and review it confidently |
| Database | PostgreSQL for documents, metadata, and ingest history |
| Semantic retrieval | pgvector for passage embeddings, evaluated against a held-out question set before acceptance |
| Text retrieval | PostgreSQL full-text search for keyword and citation lookup; combined with vector results where evaluation supports it |
| Background work | Separate ingestion workers for parsing, embedding, and indexing, so uploads never block the interface |
| Messaging and notification | Durable ingest state in PostgreSQL; ZeroMQ PUB/SUB notifications to wake the interface when new material is ready; missed updates recovered by re-reading the database |
| User interface | HTML-based local web view; no JavaScript framework mandated for the first version |
| Configuration | Versioned YAML for ingest pipelines and retrieval settings, validated against a schema; secrets kept out of the repository |
| Testing | Fast pytest suite for units and boundaries; separate integration suite for ingestion and retrieval; live model checks marked and run explicitly; --durations reviewed when the suite slows |
| Acceptance principles | A change is done when documents ingest end to end, cited answers reproduce from stored sources, and missed notifications still converge on re-read |
The value appears the moment the machine proposes something. Suppose the agent suggests adding a dedicated vector database. The charter turns that from a stylistic debate into an engineering question: what retrieval behaviour does the current PostgreSQL and pgvector foundation fail to deliver, and what evaluation would demonstrate the difference? The agent is welcome to make the case — significant improvements should be adopted — but it must explain why the foundation is insufficient rather than introducing new infrastructure by default. The same applies to a new framework, a new queue, or a new configuration scheme: justify the departure, update the charter if the argument holds, and let the next task start from the revised position.
Readers building something entirely different should take the shape of this document, not its particular answers. The transferable part is the set of decisions to record and the discipline of referring each proposal back to them.
ALIVE as the running example
Our running system follows this pattern in its present form: a Python application backend, PostgreSQL persistence including an event log, vector and text retrieval where needed, ZeroMQ notifications alongside durable stored events, a browser-based interface, declarative workflow definitions, and testing with independent review.
That describes the current shape of the work, not its history. I make no claim here that every element was selected at the start or that the architecture has remained unchanged; what was tried, replaced, or left experimental belongs to the later retrospective. The durable-log plus notification pattern above is the part worth carrying forward: store what happened where it survives restarts, announce it over channels designed for timeliness, and let late or restarted readers converge by re-reading the store.
What the charter makes possible
Choosing slowly before building quickly sounds unfashionable, and it is. But once the foundations are settled and written down, each delegated task becomes narrower: the machine works within stated constraints, proposes against a known baseline, and is judged on behaviour rather than taste.
Once I know what I am building on, one more decision belongs ahead of the work: which intelligence does which part of it. That is the subject of Configuring Your AI.