Case study: FluidSoftware
Framing a project so AI agents can build parts without losing the whole
A zero-dependency Python 3.11+ project framing CLI, starter kit and matched Research Navigator comparison for building modular software with AI coding agents.
How do you build fast with AI agents without losing the whole?
Working local starter kit and CLI (v0.1.1 at commit 6fdee0d; initial freeze v0.1.0-frozen at aa770b8). Python 3.11+ standard library only. Public implementation on GitHub.
When an AI coding agent starts in an empty folder or reads one long chat transcript, small tasks often succeed while larger builds drift: modules duplicate each other's helpers, interfaces shift mid-run, and early unit tests pass against mocks that do not match the connected program.
On the front page of this site, the Baldies remake shows one side of fluid software: rebuilding an existing program from the outside so its rules live in plain tables you can change while it runs. FluidSoftware (fluid.py and its starter-kit/) is the build-time companion for new software. Before dispatching builders, I materialize the project frame first: a whole-project map (FRAME.md), native module contracts (areas/*/MODULE.md) with disjoint file ownership, scoped context packets, and SHA-256 delivery and acceptance receipts.
I designed the workflow and evaluation protocol and directed the team (the AI coding agents I direct) to build the 1,264-line Python CLI (60,109 bytes across 6 standard-library files), package the self-hosted foundation, and run the matched Research Navigator comparison on October 23, 2026.
Three separate dependency graphs and three-tier context
A single dependency list mixes three different questions: what sits inside a capability area, whose interface a module consumes, and whether a builder must wait before starting. fluid.py separates them into three directed acyclic graphs (DAGs) validated by fluid/model.py:
parent(containment hierarchy): Optional capability grouping used when rendering ancestor context.depends_on(semantic contract): Declares that a consumer module relies on a provider's interface. Builders can code against a pinned contract in parallel, and any later byte change to an upstream provider's owned files or evidence marks downstream deliveriesstale.build_after(dispatch gate): Blocksfluid.py claimuntil the prerequisite module has been delivered or accepted.
fluid.py validates three separate DAGs, materializes module contracts, scopes builder context, and tracks SHA-256 staleness across semantic consumers.Self-hosted foundation check
Before testing the workflow on an external application, the tool packaged its own four responsibilities (model, frame, state, and cli) through foundation.project.json and workflow/prepare_foundation.py.
Across a 15-event lifecycle check, fluid.py exercised read-only frame preview, atomic hard-link file creation, repeat application preserving active state, exclusive lock contention on .fluid/state.lock, and transitive staleness propagation when state and frame delivered after cli, followed by invalidate and renewed acceptance (accepted = 4, stale = 0, integrity_issues = 0). Release v0.1.1 (6fdee0d) also tightens validate_brief() so falsey non-null parent values ("", 0, false) are rejected rather than coerced to None; the original October 23 freeze remains tagged at v0.1.0-frozen (aa770b8).
Inspect the self-hosted foundation.project.json 3-DAG contract & SHA-256 state receipt
{
"project": "fluid-foundation",
"modules": [
{ "id": "model", "owns": ["fluid/model.py"], "depends_on": [], "build_after": [] },
{ "id": "state", "owns": ["fluid/state.py"], "depends_on": ["model"], "build_after": ["model"] },
{ "id": "frame", "owns": ["fluid/frame.py"], "depends_on": ["model", "state"], "build_after": ["model"] },
{ "id": "cli", "owns": ["fluid/cli.py", "fluid.py"], "depends_on": ["model", "state", "frame"], "build_after": ["state", "frame"] }
],
"verification": { "accepted": 4, "stale": 0, "integrity_issues": 0 }
} Matched comparison: building a four-document Research Navigator
On October 23, 2026, I ran a paired comparison (PROTOCOL.md and RESULTS.md) to see how a fresh builder given a 4-module Fluid frame (navigator.project.json: parsing, storage, query, cli) compared against a fresh builder using an ordinary workflow on the same task brief (TASK_BRIEF.md).
Both builders had to write an offline Python 3.11+ CLI (navigator.py with index, search, show, export, and status) over four real documents (two Markdown files, one dense plain-text file, and a 223-paragraph Word .docx containing 81 table paragraphs), preserving SHA-256 provenance, native line and paragraph locators, and stale-source detection.
| Measure | Foundation-assisted arm (runs/foundation) | Ordinary-workflow control (runs/control) |
|---|---|---|
| Delivered architecture | 4 framed modules (nav/parsing.py, nav/storage.py, nav/query.py, nav/cli.py) + nav/contracts.py; 87 indexed passages | 4 self-organized modules (navigator.py, nav_sources.py, nav_index.py, nav_search.py); 92 indexed passages |
| First-pass automated evaluator | 11 pass, 1 fail (freshness), 1 manual-review gate | 11 pass, 1 fail (markdown_primary), 1 manual-review gate |
| Cause of first-pass failure | Frozen evaluator bug: Path.read_text() converted CRLF to LF before checking a passage from resuse.txt that preserved 3 CRLF pairs | Frozen evaluator bug: ATX-only heading list rejected a valid CommonMark level-2 setext heading (\text{integration risk} underlined by -) |
| Adjudicated acceptance on unchanged first delivery | 13 / 13 protocol rows satisfied (adjudication, manual review) | 13 / 13 protocol rows satisfied (adjudication, manual review) |
| Product repairs required | 0 (unchanged first-delivery bytes) | 0 (unchanged first-delivery bytes) |
Limits: Both fresh builders completed working, independently accepted applications on their first delivery on October 23, 2026. Although the assisted builder finished its implementation pass in a shorter interval than the control builder, target-specific frame authoring (navigator.project.json) was completed before dispatch without separate timing, both arms required manual and adjudication review, and this is a single-pair run (n = 1) on one machine with 0 external reproductions. It does not establish a net speed multiplier.
Auditing the evaluator before requesting code repairs
The sharpest engineering finding from the comparison came from auditing the evaluation harness before requesting code repairs (EVALUATOR-REVIEW.md):
- Universal-newline stripping in
evaluate.py(adjudicate_newlines.py): In the Foundation arm, commands 29 to 31 exported lines 14 to 17 ofresuse.txt, which contain three CRLF line endings. The exported Markdown file (sha256: b20241bb...) preserved all three CRLF pairs, but the frozen evaluator read the report withPath.read_text()(normalizing CRLF to LF) and searched for the raw CRLF JSON string. Re-running the interrupted freshness sequence with byte-preserving reads passed all 16 CLI checks on the unchanged snapshot. - ATX-only heading oracle vs. CommonMark setext rules (
adjudicate_headings.py): In the Control arm, searchinglargest coherent responsibilityindeep-research-report.mdreturned\text{integration risk}in its parent heading chain because line 298 was followed by a single-on line 299 inside a display equation. Running the reference parsermarkdown-it-py4.0.0 in itscommonmarkpreset confirmed that CommonMark 0.31.2 §4.3 parses lines 298 to 299 as anh2setext heading, and all 4 remaining search and show checks passed on unchanged code.
If I had sent those two red rows back to the builders as repair requests, both agents would have weakened their source fidelity or parser generality to appease a broken check. The starter workflow now explicitly checks failed assertions against original sources before requesting code repairs.
Run the CLI, unit tests and comparison demo
Requires Git and Python 3.11 or newer. No third-party packages, accounts, or API keys are used. These commands validate and materialize the self-hosted foundation, run the 7 unit and lifecycle checks, and execute both delivered Research Navigators against a generated sample corpus (.md, .txt with CRLF, and .docx with a Word XML table):
git clone https://github.com/thisisntjon/fluid-software.git
cd fluid-software
git checkout 6fdee0dddf8a446bdbd298b81db5a9df66e0232a
python fluid.py validate foundation.project.json
python fluid.py frame foundation.project.json ./tmp-self-host --apply
python ./tmp-self-host/fluid.py verify ./tmp-self-host --json
python -m unittest discover -s tests -v
python examples/research-navigator/run_demo.py You can also open showcase/fluid-architecture-showcase.html locally in any browser for an offline interactive SVG walkthrough of the decomposition stopping rule, the three DAGs, the three-tier context lens, and SHA-256 staleness propagation.
Primary artifacts and source links
thisisntjon/fluid-softwareat commit6fdee0d(releasev0.1.1) and frozen initial commitaa770b8(v0.1.0-frozen).docs/FOUNDATION-CONTRACT.mdanddocs/DESIGN.md: schema v1 specification, exit codes, and design trade-offs.starter-kit/START.mdandstarter-kit/BUILD.md: instructions for framing a greenfield build with your existing coding agent.research/SYNTHESIS.mdandresearch/scaffold-scaling.md: literature synthesis on why decomposition must stop at the largest coherent buildable unit.examples/research-navigator/RESULTS.md: full comparison report, adjudication records, and both delivered navigators.
Questions or notes on your own runs? Reach me at jon@simoneresearch.com or on the contact page.