# Formal - Strongly-Typed Multiagent Ant-Robot Colony Exemplar
**This is an active project** in the `projects/` directory, discovered and
executed by infrastructure discovery functions. Public exemplar roster and
comparison: [`projects/AGENTS.md`](../../AGENTS.md#permanent-canonical-exemplars).
Manuscript semantics: [`docs/guides/manuscript-semantics.md`](../../../docs/guides/manuscript-semantics.md).
Decision memory and verifier hardening follow
[`docs/rules/memory_and_decision_records.md`](../../../docs/rules/memory_and_decision_records.md):
use nearby `WHY:` comments only for surprising local choices, keep volatile
counts generated, and add negative controls for verifier-like gates.
## Layer contract
| Surface | Rule |
| --- | --- |
| `src/template_formal/` (domain) | Pure typed domain code — ADTs, session types, affine handles, storage, network, agent, colony — **no** direct `infrastructure` imports |
| `scripts/` | Thin orchestrators; may import `infrastructure/` and `src/` |
| `formal/` | Optional Lean 4 + TLA+ side-specs, wired to `scripts/check_formal_specs.sh` only — never decorative |
| Live counts | Do not hardcode measured test totals or coverage % in this file |
## Overview
A research project demonstrating illegal-state-unrepresentable design
applied to a genuinely decentralized domain: an ant-robot colony where each
agent owns its own on-disk SQLite database and its own in-process,
fault-injectable network endpoint — no shared global state. The typed
surface is the research subject itself: algebraic data types (`Result`),
nominal `NewType` identifiers, a session-typed protocol state machine,
affine-discipline resource handles that are runtime-guarded (not
compiler-proved) against reuse, a storage layer framed as a functor
`Schema -> Set`, and a per-agent decision loop that minimizes a closed-form
expected-free-energy quantity (Friston 2005 framing).
## Key Features & Capabilities
### Type Architecture
- **ADTs**: `Result[T, E]` as `Ok`/`Err` frozen dataclasses with a
`Literal` tag field; `match`-exhaustiveness is mypy-enforced via
`assert_never` on the missing arm.
- **Nominal IDs**: `AgentId`/`MessageId`/`TxnId` as distinct `NewType`
wrappers over `uuid.UUID` — a compile-time-only distinction; indistinguishable
at runtime.
- **Session types**: `IdleSession → HandshakingSession → EstablishedSession →
ClosedSession`, each a separate class; phase-specific methods do not exist
on the wrong phase (an `AttributeError` under mypy --strict, not a caught
exception).
- **Affine-discipline handles**: `TransactionHandle`/protocol-phase objects
are frozen + `__slots__` with a private consumed flag, raising at runtime
on reuse — Python has no linear/affine type system, so this is a
*discipline*, never claimed as a compiler guarantee.
### Research Quality Assurance
- **mypy-as-oracle test suite**: `tests/test_mypy_oracle.py` runs
`mypy --strict` as a real subprocess against every known-bad and
known-good fixture under `tests/mypy_fixtures/` (non-zero/zero exit
respectively) and against the real `src/` tree (zero exit expected) —
proof-of-detection, not just a typed signature. See "Adding a mypy-oracle
fixture" below for the auto-discovery convention.
- **Zero-mock testing**: real on-disk SQLite files via `tmp_path`, a real
in-process message bus with seeded fault injection, real `mypy --strict`
subprocess invocations.
- **Paired static+dynamic proofs**: every phase-transition/affine-reuse ISC
has both a mypy negative-control fixture and a runtime-raise unit test.
- **Fault-injected negative controls**: every protocol happy-path test has a
paired fault-injected test (drop/corrupt modes) asserting a typed
`Result.Err`, never a crash or silent phase advance.
### Publication-Ready Output
- **Manuscript with explicit claim-scoping section**: "What mypy --strict
proves vs. what is a runtime discipline," citing ISC numbers for every
strong claim.
- **Active Inference framing**: Friston (2005) cited in the decision-loop
docstring and manuscript, plus the Ehresmann & Vanbremeersch Memory
Evolutive Systems bridge from category theory to collective biological
organization.
## Directory Structure
```mermaid
flowchart TB
P[projects/templates/template_formal]
P --> SRC[src/template_formal
types · storage · protocol · network · agent · colony]
P --> SC[scripts
Thin orchestrators]
P --> T[tests
zero mocks · mypy_fixtures/]
P --> FRM[formal
optional Lean 4 + TLA+ side-specs]
P --> M[manuscript]
P --> PY[pyproject.toml
Pytest/coverage/mypy config]
SRC --> SRC_F[types/ids.py · types/result.py · types/phase.py ·
storage/schema.py · storage/db.py · storage/transaction.py ·
protocol/session.py · protocol/errors.py · network/bus.py ·
agent/agent.py · colony/pheromone.py · colony/experiment.py ·
colony/stats.py · colony/nullmodel.py · colony/sweep.py ·
colony/demo.py · colony/visualization.py · colony/analysis.py]
SC --> SC_F[00_setup_environment.py · 02_run_analysis.py]
T --> T_F[mypy_fixtures/ · storage/ · protocol/ · network/ · agent/ · colony/ ·
test_mypy_oracle.py · test_types_ids.py · test_types_result.py]
M --> M_F[00_abstract → 05_results_discussion.md ·
config.yaml · config.yaml.example ·
preamble.md · references.bib]
classDef d fill:#0f172a,stroke:#0f172a,color:#fff
classDef pkg fill:#1e3a8a,stroke:#0f172a,color:#fff
classDef f fill:#0f766e,stroke:#0f172a,color:#fff
class P d
class SRC,SC,T,FRM,M,PY d
class SRC_F,SC_F,T_F,M_F f
```
## Installation/Setup
Install dependencies from the **repository root** with `uv sync` (see root
[`pyproject.toml`](../../../pyproject.toml)). `template_formal/pyproject.toml`
pins pytest/coverage/mypy settings and documents the project name for
isolated runs of this tree.
## Usage Examples
```bash
# From the repository root — run the demo colony pipeline
uv run python projects/templates/template_formal/scripts/02_run_analysis.py
# Run tests with coverage
uv run pytest projects/templates/template_formal/tests/ --cov=projects/templates/template_formal/src --cov-fail-under=90
# mypy --strict oracle
uv run mypy --strict projects/templates/template_formal/src
```
## Adding a mypy-oracle fixture
`tests/test_mypy_oracle.py` discovers fixtures by filename glob, not by a
hardcoded list — this is what "no test-file edit needed" actually means in
this codebase, precisely scoped:
- **Adding a `good_.py` fixture** genuinely needs zero edits to
`test_mypy_oracle.py`: drop the file under `tests/mypy_fixtures/`, and
`_good_fixtures()`'s `FIXTURES_DIR.glob("good_*.py")` plus the
`@pytest.mark.parametrize` above `test_known_good_fixture_is_accepted_by_mypy_strict`
pick it up automatically on the next test run, asserting `mypy --strict`
accepts it (exit 0).
- **Adding a `bad_.py` fixture** is discovered the same way via
`_bad_fixtures()`'s `glob("bad_*.py")`, but is **not** zero-edit: you
must also add an entry to `_EXPECTED_BAD_FIXTURE_SUBSTRINGS` keyed by
the exact filename, binding the fixture to the specific `mypy --strict`
error substring it is supposed to trigger. This is deliberate, not an
oversight — a generic `"error:" in result.stdout` check is a hollow gate
(a fixture whose *intended* illegal state stopped triggering, but which
still emits some unrelated error, would keep passing for the wrong
reason). A new `bad_*.py` fixture with no dict entry fails loudly with a
`KeyError` rather than silently passing under a substring-free fallback.
Either way, name the file `bad_.py` or `good_.py`
(snake_case, one file per invariant) — the prefix is what the two globs
key on.
## Protocol for AI Agents
**Critical Directive**: Before modifying this project, AI agents *must*:
- **Add new Ideal-State Criteria to `ISA.md` before writing code**, not
after. This template's manuscript (@sec:type-architecture,
@sec:results-discussion) cites specific ISC numbers for every strong
claim it makes — a change that isn't backed by an ISC in `ISA.md` has no
stable identifier for the manuscript, `tests/mypy_fixtures/`, or
`TODO.md` to point at. Follow `ISA.md`'s existing numbering convention
(`ISC-N`, grouped by module, anti-criteria called out explicitly) when
appending — do not renumber existing ISCs, only append new ones or
supersede one via a `## Decisions`/`## Changelog` entry, per this repo's
`docs/rules/memory_and_decision_records.md`.
- Never add `Any` or unjustified `# type: ignore` to `src/template_formal/types/`
(ISC-7) — every suppression needs an inline justification comment.
- Never claim compile-time linear or dependent type guarantees anywhere in
source docstrings or manuscript prose (ISC-44) — Python has neither;
affine handles are a runtime-guarded discipline only.
- Add a paired negative-control test (mypy fixture or runtime-raise unit
test) for every new strong typing claim before considering it done.
- Keep `scripts/` thin — business logic belongs in `src/template_formal/`.
- Never use `MagicMock`/`unittest.mock`/`mocker.patch` anywhere in `tests/`.
## Testing
```bash
uv run pytest projects/templates/template_formal/tests/ -v
uv run pytest projects/templates/template_formal/tests/ --cov=projects/templates/template_formal/src --cov-report=html
```
## Optional formal side-spec
`formal/lean/` (Lean 4) and `formal/tla/` (TLA+) model the handshake
protocol. Both are wired to one real runnable check each via
`scripts/check_formal_specs.sh` — see [`TODO.md`](TODO.md) for the decision
record (ISC-35/36: shipped, not cut).
## Anti-patterns (do not do these)
- Do not add a compiler-level linearity/dependent-type claim anywhere —
grep for `"dependent type"` and `"linear type"` outside an explicit
limitations section must return zero matches (ISC-44).
- Do not let a fault-injection mode default to permanently disabled in a way
that makes a fault-injected test vacuously pass on an all-happy-path run
(ISC-68).
- Do not test coverage by asserting type annotations exist rather than
exercising behavior (ISC-67) — every covered line needs a behavioral
assertion reachable from it.
- Do not add a vestigial/unwired `.lean`/`.tla` file — every formal artifact
under `formal/` must be reachable from `scripts/check_formal_specs.sh`.
- Do not use `:memory:`-only SQLite for any claim about durable per-agent
state (ISC-66) — use a real `tmp_path` file.
## More Information
See [README.md](README.md) for the project overview and quick start.
## See Also
- [Root AGENTS.md](../../AGENTS.md) - Template documentation
- [`../../AGENTS.md`](../../AGENTS.md#permanent-canonical-exemplars) — public exemplar roster
- [`manuscript/references.bib`](manuscript/references.bib) — full bibliography