Skip to content

Programming with human language: an enforceable codebase

Published 15 October 2026 · Bella Belgarokova · 12 min read.

Insights

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.

PartPermitted constructionHome
ConstantsFixed, typed, immutable valuesconstants.py or constants/
PrimitivesDistinct scalar type declarationsprimitives.py or primitives/
EnumsClosed sets of named alternativesenums.py or enums/
ObjectsImmutable products and declared sums; collection and capability type declarationsobjects.py or objects/
MorphismsIndividual pure operations, transformations and predicatesmorphisms.py or morphisms/
PipelinesNamed compositions of existing pure operationspipelines.py or pipelines/
EffectsFunctions performing external interactionseffects.py or effects/
RuntimeConfiguration source selection, capability assembly and resource lifecycleruntime.py or runtime/
WorldThe external environmentNo 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: ClientSecret

Environment 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 excerptHome
TESTNET_URL and default valuesConstants, typed with declared primitives
ExchangeConfig and Credentials fieldsObjects, using primitives and enums such as Currency
Credentials.from_envEffects, in effects/environment.py
Reading the TOML file in load_configEffects, in effects/files.py
_buildMorphisms, in morphisms/configuration.py
Rules inside validateIndividual predicates in morphisms/configuration.py
_build followed by validationA named composition in pipelines/configuration.py
Choosing the file or defaults and starting the loadRuntime, 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.

ViolationWhat the check inspectsRequired correction
Enum declared in morphismsDeclaration shape and source locationMove the enum to enums and import it
Record declared in effectsRecord declaration and locationMove the data declaration to objects
Operational method inside an objectClass bodyMove behaviour to its function category
Raw type in a function signatureAnnotation and resolved type originUse the declared type from its owner
Inline collection or union outside objectsType expression and locationDeclare the structure in objects and reference it
New calculation inside a pipelineFunction body and called operationsExtract an owned morphism
External interaction inside a morphismImports, capability types and callsMove the interaction into effects
Configuration read in constantsInitialiser and dependenciesMove 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.toml

Directories 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.toml

tests/ 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.