Programming with human language: an enforceable codebase
Published 15 October 2026 · Bella Belgarokova · 12 min read.
A coding agent needs construction rules that can be checked against the code it produces. Every construction needs a permitted form, a defined home and explicit dependency boundaries. Those rules give a control layer something concrete to enforce while the agent works.
A strongly defined codebase makes correction actionable. An enum declared in a functions module is a placement violation. A raw string type introduced in a function signature violates type ownership. A database call inside a pure computation crosses an execution boundary. Each violation can be identified against a declared rule, with a correction that follows from the construction model.
Define the system before constructing it
The previous article established three definitions. The domain profile declares the concepts, their meanings and relationships. Invariants declare the properties and construction rules every implementation must preserve. Intent declares what the particular system should do.
Before moving from this base—profile, invariants and intent—to implementation, we need to consider the codebase in which the agent will work.
The codebase needs a construction contract: which constructions are permitted, where they belong and how they may depend on one another. The agent should not have to invent an architectural organisation while implementing each requirement. These decisions need to be established in a form that can be checked as construction proceeds.
Broad architectural names leave considerable room for those decisions. A service, repository or controller can contain data declarations, stored dependencies, calculations, external calls and mutable state. Its name does not establish which of those constructions are permitted or how a check should recognise a violation.
One configuration module, several responsibilities
The following excerpt comes from a configuration module produced by a coding agent for a trading engine. Other declarations and validation rules are omitted:
TESTNET_URL = "https://test.deribit.com"
@dataclass(frozen=True)
class ExchangeConfig:
base_url: str = TESTNET_URL
currency: str = "ETH"
request_timeout_s: float = 10.0
order_label: str = "strangle-engine"
@dataclass(frozen=True)
class Credentials:
client_id: str
client_secret: str
@classmethod
def from_env(cls, env: dict[str, str] | None = None) -> "Credentials":
env = os.environ if env is None else env
client_id = env.get("DERIBIT_CLIENT_ID", "").strip()
secret = env.get("DERIBIT_CLIENT_SECRET", "").strip()
if not client_id or not secret:
raise ConfigError(
"DERIBIT_CLIENT_ID and DERIBIT_CLIENT_SECRET must be set in the environment "
"(testnet keys only; see .env.example)."
)
return cls(client_id=client_id, client_secret=secret)
def _build(cls: type, data: dict[str, Any], path: str) -> Any:
hints = get_type_hints(cls)
known = {f.name: hints[f.name] for f in fields(cls)}
# Remaining recursive construction omitted.
def validate(cfg: Config) -> Config:
ex, st, rk, xt, sg, en = cfg.exchange, cfg.strategy, cfg.risk, cfg.exit, cfg.signal, cfg.engine
if ex.base_url.rstrip("/") != TESTNET_URL:
raise ConfigError(
f"Refusing to run: exchange.base_url must be {TESTNET_URL!r} (got {ex.base_url!r}). "
"This engine only ever trades on the Deribit TEST environment."
)
# Remaining validation rules omitted.
return cfg
def load_config(path: str | Path | None) -> Config:
if path is None:
return validate(Config())
p = Path(path)
# File checks and error handling omitted.
with p.open("rb") as fh:
data = tomllib.load(fh)
cfg = _sections(data)
return validate(cfg)The full module combines constants, configuration records, default values, credential loading, reflective object construction, validation and file I/O. The validation function checks exchange restrictions, strategy ranges, risk limits, exit rules, signal thresholds and polling intervals in one place.
This is valid Python. It has named sections, frozen records and a clear configuration-related purpose. The construction problem remains: “configuration” groups together several different responsibilities without establishing their internal boundaries.
Credentials declares data and reads the environment through a class method. _build accepts an arbitrary class and returns Any, so its signature does not identify the object it constructs. load_config reads a file, constructs configuration and invokes validation. Distinct quantities and identities share raw str, float and int representations.
If an agent keeps adding features to this module as it stands, fields, defaults, loading rules and validation branches accumulate in the same place. A new risk limit becomes another branch in validate, which can be exercised only by constructing a complete Config. A new credential source adds another environment read inside a data record.
A functional foundation
I studied F# for one semester in my first year at university. My subsequent work was in object-oriented environments.
Agent-driven implementation makes those functional foundations relevant again. Scott Wlaschin’s Functional Programming Design Patterns develops design around types, functions and composition. His Designing with Types series shows how types make distinctions and design decisions explicit.
Those principles give construction a concrete basis: immutable data declarations, pure functions, pure pipelines and explicit I/O functions. Types declare what the code works with. Pure functions operate on supplied values, and pure pipelines compose those operations. Explicit I/O functions own interaction with the external environment.
The model below turns those principles into repository rules. Its usefulness depends on whether the rules can be checked and whether they help preserve the specified system through successive changes.
The codebase is a contract
Shape means the observable structure of a construction: its declaration, typed signature, body and dependencies. The model assigns each construction a responsibility and a permitted location.
| Part | Permitted construction | Home |
|---|---|---|
| Constants | Fixed, typed, immutable values | constants.py or constants/ |
| Primitives | Distinct scalar type declarations | primitives.py or primitives/ |
| Enums | Closed sets of named alternatives | enums.py or enums/ |
| Objects | Immutable products and declared sums; collection and capability type declarations | objects.py or objects/ |
| Morphisms | Individual pure operations, transformations and predicates | morphisms.py or morphisms/ |
| Pipelines | Named compositions of existing pure operations | pipelines.py or pipelines/ |
| Effects | Functions performing external interactions | effects.py or effects/ |
| Runtime | Configuration source selection, capability assembly and resource lifecycle | runtime.py or runtime/ |
| World | The external environment | No code directory |
A small package can use one file per construction category. As the package grows, each file can become a directory whose modules are organised by domain meaning. For example, morphisms.py can become morphisms/ with modules for configuration, instruments and risk. The directory retains its construction responsibility, and the same contract applies to every module within it.
These are placement responsibilities, not stages through which every operation must pass. A pure library may need no effects or runtime. Within the responsibilities it uses, every declaration still has to conform.
A declaration has one permitted home
Consider a supported-currency enum for the configuration example:
class Currency(Enum):
ETH = "ETH"Its shape identifies it as an enum. It therefore belongs in enums.py or a module under enums/. Declaring it in morphisms.py violates the contract even if the application and its tests still work.
A check can parse the source into a syntax tree, recognise the enum declaration and compare its location with the permitted home. The same inspection must cover nested declarations: placing the enum inside a function does not change its category.
Importing Currency into a morphism is permitted. Declaring it there is not. This distinction gives the agent a precise correction: move the declaration to its owner and import it where needed.
The same rule applies across the codebase. A record declaration in effects belongs in objects. An operational method inside a record violates the accepted object shape. A scalar type introduced in a pipeline belongs in primitives.
The contract therefore governs what each location may contain as well as where each construction belongs. A module does not become conforming because its filename is correct.
Types also have owners
Every value has a declared type. Built-in scalar and collection representations may be introduced only in primitives, enums and objects. The remaining parts use those declarations rather than creating their own representations.
For example, scalar distinctions are declared in primitives:
# primitives.py
from typing import NewType
ExchangeUrl = NewType("ExchangeUrl", str)
ClientId = NewType("ClientId", str)
ClientSecret = NewType("ClientSecret", str)
IsTestnet = NewType("IsTestnet", bool)A record is declared in objects:
# objects.py
from dataclasses import dataclass
from .primitives import ClientId, ClientSecret
@dataclass(frozen=True)
class Credentials:
client_id: ClientId
client_secret: ClientSecretEnvironment access is absent from this declaration. The record contains declared values; separate functions own their loading and validation. Collection representations, when needed, are also declared in objects and referenced by name elsewhere.
A predicate consumes a declared URL and returns a declared decision:
# morphisms.py
from .constants import TESTNET_URL
from .primitives import ExchangeUrl, IsTestnet
def is_testnet(url: ExchangeUrl) -> IsTestnet:
return IsTestnet(url.rstrip("/") == TESTNET_URL)Changing the return annotation to bool would introduce a raw type outside its permitted declaration locations. Copying the original loader’s dict[str, Any] into a function signature would introduce an inline collection representation and an unchecked value type. The operation needs a declared input whose structure is owned and whose meaning is explicit.
Constants follow the same ownership rule:
# constants.py
from typing import Final
from .primitives import ExchangeUrl
TESTNET_URL: Final[ExchangeUrl] = ExchangeUrl("https://test.deribit.com")The primitive owns the representation; the constant fixes a value of that declared type. Initialisation may construct the declared immutable value. It may not read configuration or perform external work.
The rule covers function signatures, local annotations, inline type aliases and capability declarations. A database connection or network capability also needs a declared type binding in the permitted type-owning files. The binding represents the capability without introducing a behavioural service class.
This is representation ownership at source level. Python still evaluates underlying scalar and collection operations. Its NewType declarations provide static distinctions; they do not perform runtime validation. External values need validation according to the specification.
A checker must therefore do more than look for missing annotations. It must identify forbidden raw types, resolve referenced declarations and establish whether those declarations have valid owners. A name alone is insufficient if it merely disguises an unchecked or prohibited representation.
Functions have construction boundaries
Morphisms own individual pure operations. Pipelines compose existing pure operations. Effects perform external interactions. Runtime assembles the concrete execution.
These functions can share syntax, so their classification also depends on their bodies and dependencies.
A configuration pipeline may compose existing construction and validation functions. If it introduces a new rule about permitted risk or strategy ranges inside that composition, it has taken ownership of an operation that belongs in morphisms. The correction is to give the rule an explicit function and call it from the pipeline.
A morphism receives the facts it needs. If it imports a database client or fetches a missing fact, it has crossed the pure boundary. The external interaction belongs in effects; the resulting declared value becomes an input to the pure computation.
An effect does not become a pipeline because it composes several calls. Its dependency on external capabilities keeps it within effects. Runtime supplies those capabilities and manages their configuration and lifetime. Domain calculations remain in their declared owners.
The dependency contract follows those responsibilities. Pure modules do not import effects or runtime. Effects may consume pure definitions and operations, but do not depend on runtime. Runtime assembles the required parts. World identifies the environment being observed or changed, rather than another module to import.
Applied to the configuration module, the contract gives each of its responsibilities a home:
| Construction in the excerpt | Home |
|---|---|
TESTNET_URL and default values | Constants, typed with declared primitives |
ExchangeConfig and Credentials fields | Objects, using primitives and enums such as Currency |
Credentials.from_env | Effects, in effects/environment.py |
Reading the TOML file in load_config | Effects, in effects/files.py |
_build | Morphisms, in morphisms/configuration.py |
Rules inside validate | Individual predicates in morphisms/configuration.py |
_build followed by validation | A named composition in pipelines/configuration.py |
| Choosing the file or defaults and starting the load | Runtime, in runtime/startup.py |
Configuration becomes a domain module name within several categories rather than a category of its own.
Turn the contract into corrections
Each construction rule needs a detector and a diagnostic that identifies the violated obligation.
| Violation | What the check inspects | Required correction |
|---|---|---|
| Enum declared in morphisms | Declaration shape and source location | Move the enum to enums and import it |
| Record declared in effects | Record declaration and location | Move the data declaration to objects |
| Operational method inside an object | Class body | Move behaviour to its function category |
| Raw type in a function signature | Annotation and resolved type origin | Use the declared type from its owner |
| Inline collection or union outside objects | Type expression and location | Declare the structure in objects and reference it |
| New calculation inside a pipeline | Function body and called operations | Extract an owned morphism |
| External interaction inside a morphism | Imports, capability types and calls | Move the interaction into effects |
| Configuration read in constants | Initialiser and dependencies | Move external reads into effects, and source selection and assembly into runtime |
For example, a diagnostic can say:
File: src/engine/morphisms.py
Declaration: Currency
Violation: enum declarations are permitted only in enums.py or enums/.
Correction: move Currency to its declared owner and import it here.The check supplies a construction obligation. The agent does not need to negotiate whether another service, wrapper or layer would be a better organisation.
Hooks can run applicable checks while the agent makes changes. The agent receives the diagnostic, corrects the violation and runs the checks again. Pre-commit checks inspect the proposed change and block it while enforced violations remain. The same checks can run in CI so conformance does not depend on a local hook being installed.
A written rule becomes enforcement only when the mechanism actually runs, reports a violation and requires its resolution.
What enforcement establishes
Construction conformance and semantic correctness require different evidence.
A placement check can establish that Currency lives in enums. Type checks can establish that functions use the declared distinctions. Dependency checks can detect prohibited imports and capability access. Behavioural and integration checks establish whether the implemented decisions and external interactions fulfil the specification. Applicable proofs establish stated properties under their explicit assumptions.
A correctly placed validator can still enforce the wrong risk limit. A test that successfully loads a configuration can still leave environment reads inside a data declaration or raw types in operational signatures. Both obligations remain.
The language binding determines what the control layer can inspect and enforce. In Python, syntax and dependency analysis can enforce declared restrictions, but do not prove purity or semantic correctness for arbitrary code. A detector's stated coverage must match what it actually checks.
The model can be applied across typed languages through explicit bindings for declarations, dependencies and verification.
The repository in two forms
The repository brings the definitions, implementation and checks together. The following two layouts use the same construction contract. They differ in how much room each category needs.
One file per construction category
A small implementation keeps each category in one file. Paths are relative to the repository root; Python package initialisation files are omitted for readability.
repository/
├── .claude/
│ └── hooks/
├── specifications/
│ └── INTENT.md
├── src/
│ └── engine/
│ ├── constants.py
│ ├── primitives.py
│ ├── enums.py
│ ├── objects.py
│ ├── morphisms.py
│ ├── pipelines.py
│ ├── effects.py
│ └── runtime.py
├── tests/
├── tools/
│ └── control/
├── PROFILE.md
├── INVARIANTS.md
├── .pre-commit-config.yaml
└── pyproject.tomlDirectories with semantic organisation
As the implementation grows, category files become directories. Inside each category, modules group related declarations or operations by meaning. An instrument operation still belongs in morphisms; its domain meaning places it in morphisms/instruments.py.
repository/
├── .claude/
│ └── hooks/
├── specifications/
│ └── INTENT.md
├── src/
│ └── engine/
│ ├── constants/
│ │ ├── configuration.py
│ │ └── market.py
│ ├── primitives/
│ │ ├── identifiers.py
│ │ ├── quantities.py
│ │ └── configuration.py
│ ├── enums/
│ │ ├── currencies.py
│ │ └── orders.py
│ ├── objects/
│ │ ├── configuration.py
│ │ ├── instruments.py
│ │ └── positions.py
│ ├── morphisms/
│ │ ├── configuration.py
│ │ ├── instruments.py
│ │ └── risk.py
│ ├── pipelines/
│ │ ├── configuration.py
│ │ ├── entry.py
│ │ └── exit.py
│ ├── effects/
│ │ ├── environment.py
│ │ ├── files.py
│ │ ├── venues.py
│ │ └── persistence.py
│ └── runtime/
│ ├── startup.py
│ └── shutdown.py
├── tests/
├── tools/
│ └── control/
├── PROFILE.md
├── INVARIANTS.md
├── .pre-commit-config.yaml
└── pyproject.tomltests/ contains construction, behavioural and integration verification. tools/control/ contains the construction detectors. Hooks under .claude/hooks/ invoke them during agent work, and .pre-commit-config.yaml registers the pre-commit checks. These supporting locations sit alongside the implementation categories.
The semantic modules shown are examples; the system specification determines which are needed. A repository can transition one category at a time. objects.py can become objects/ while enums.py remains a file. Placement and type-ownership checks must recognise both forms and inspect every module within a category directory.
The next article covers the control layer in detail: how rules become detectors, how hooks return actionable diagnostics, and how pre-commit checks keep unresolved construction violations out of the repository.