Wednesday, 26 August 2026

From AI Collaboration to Material Cost Service: How Codex and Claude Co-Engineered a Real Enterprise Feature

August 26, 2026. South Africa.

Yesterday I published Two AIs, One Branch, No Human Clipboard, the story of how I connected Codex and Claude so that they could collaborate directly on a real engineering problem. That first post concentrated on the mechanics: one persistent Claude CLI session, one implementation owner, one read-only reviewer, one shared engineering ledger, and a human who no longer had to copy messages between two AI applications.

This is the follow-up I could not write until the branch was almost finished.

What did the two AIs actually do together? Did the dialogue survive contact with architecture, production data, implementation, testing and security? Did the second model add genuine engineering value, or did it merely produce a convincing stream of review prose?

The short answer: Codex and Claude co-designed and reviewed a material-cost service from first principles, connected it to existing planning screens through a compatibility facade, added deterministic AI access, shaped a one-way full-catalog contract for an external quote CRM, found and corrected multiple boundary defects, and kept deployment and activation under explicit human control.

This post describes that journey without exposing customer names, commercial prices, material identifiers, private URLs, credentials or internal infrastructure details. The domain is deliberately generalised. The engineering lessons are real.


The problem was bigger than “which price is correct?”

The platform already had years of ERP-derived sales, stock and procurement data. Its product catalogue also carried historical cost references used by budget planning. The immediate symptom was a familiar one: a catalogue cost could disagree with a newer ERP-observed cost.

It was tempting to patch the displayed number. But that would have avoided the real question:

How should one platform represent several legitimate meanings of “cost” without silently mixing them, losing their provenance, or forcing every screen and external consumer to reinvent the selection rule?

A physical material could have an ERP Standard Price, an ERP Moving Average Price, a transaction valuation, stock-ledger evidence, procurement comparators and a Bill of Material roll-up. A non-physical catalogue line such as training or an on-site service could still be quoteable while having no material cost at all. Historical records pre-dated the newest ERP price-control fields. The planning application needed stable saved values, while a quote CRM needed current typed evidence and its own selling-price policy.

That is not a column-mapping problem. It is a domain boundary.


The architecture the two agents converged on

The final design kept one deterministic material-cost core and gave each consumer an appropriate contract. Internal screens did not have to understand source documents. The external CRM did not receive access to internal databases or interactive APIs. AI could explain and query the same governed result, but it could not perform financial arithmetic in a prompt.

BoundaryResponsibilityImportant constraint
Existing ERP exportsSupply sales valuation, stock and procurement evidence through existing ETL flows.No new ERP report or integration was required.
Product Master and BOM matrixOwn catalogue identity, governance metadata, approved recipe links and component quantities.Product Master does not become a second independent cost ledger.
Material Cost CoreNormalise evidence, apply a versioned policy, validate unit-of-measure compatibility, roll up BOM material costs, and return coverage/freshness/status.Deterministic server-side arithmetic; incomplete is an honest result.
Internal cost API and facadeExpose the rich result while safely projecting it into legacy unitCost-shaped consumers.Compatibility lives at the edge, not inside the core.
AI toolsQuery governed costs and evidence for authorised users.The LLM explains results; it does not select prices or calculate BOM totals.
Immutable catalogue publisherBuild one complete, versioned snapshot and deliver the exact retained bytes outbound over HTTPS.Publication failure never rolls back a successful ERP ETL publication.
Quote CRMValidate and atomically promote the snapshot, apply its own quote policy, margin and approvals, and retain the selected evidence on the quote line.No synchronous per-line dependency on the financial platform.

The blast-radius decision was especially important. Existing Product Catalogue and Budget Plan code could keep consuming a familiar cost shape while the facade gradually changed where that cost came from. The new rich result remained available for consumers that needed provenance, alternatives, coverage or warnings.

The resolution path inside the core

The architecture document also separates the evidence-selection path from the consumer contract. A request first passes catalogue governance, then resolves current price-control evidence, historical fallback or BOM component costs. The output carries explicit status and warnings; a non-material line takes a separate manual-pricing path.


The collaboration timeline

The work did not unfold as “Codex writes a plan, Claude says looks good.” The ledger records a sequence of design debates, owner decisions, implementation reviews and corrections.

StageCodex and Claude activityOutcome
1. Frame the domainCodex proposed typed cost observations and a rich material-cost result. Claude challenged storage coupling, unit-of-measure handling, freshness and what “complete” must mean.A stateless deterministic core with explicit complete, incomplete, stale, ambiguous and missing states.
2. Debate source precedenceThe agents tested Sales, Stock, purchase-order and goods-received evidence against historical and forward-looking use cases.Evidence remained labelled; no source was allowed to masquerade as another.
3. Human business decisionThe technical debate started inventing a procurement-derived planning average. I stopped it and clarified that the platform should follow the ERP price-control rule, with governed historical fallback where the newer fields did not exist.The owner decided policy once; both agents reconciled the design and recorded the superseded alternatives.
4. Close architectureClaude inspected the actual data and code paths, found an inconsistent example that implied an unapproved BOM link, and withheld a clean close until it was corrected.Architecture and external contract agreed before implementation.
5. Prepare local runtimeThe branch was made to run against the modern API backend locally before feature slices began.Implementation and ETL evidence could be exercised without designing around a retiring runtime.
6. Build in slicesCodex implemented the core, last-known valuation projection, BOM roll-up, internal API, facade, catalogue and budget consumers, deterministic AI tools and inactive external publisher. Claude reviewed every bounded diff.Each slice received an explicit approve/request-changes verdict and a recorded closure.
7. Reconcile real dataA parallel ERP backfill populated the newer price-control fields. The agents audited the refreshed data rather than assuming the migration had worked.Coverage evidence matched the intended two-era fallback.
8. Design CRM operationsThe agents added immutable staging, idempotent delivery, retained-byte retries, manual publishing and a configurable schedule—while leaving transport disabled.Code-complete did not mean externally activated.
9. Close non-material pricingClaude challenged the treatment of service, training and call-out lines. The owner clarified that these must remain quoteable but require manual selling-price entry in the CRM.Explicit governance distinguishes catalogue-cost items from manual case-by-case items without inferring from names or tiny placeholder values.
10. Govern existing catalogue dataCodex and Claude designed a dry-run-first, idempotent metadata enrichment. They debated deployment order, corrected one another’s assumptions, and preserved rollback evidence.The same reviewed mapping could be applied safely without rewriting costs or re-mirroring an entire environment.
11. Sweep operational writersClaude found a theoretical stale full-document write risk; Codex corrected the writer matrix and the owner calibrated the finding against the stable manual operating process.Real risks were documented without turning a rare operational edge into an unnecessary redesign.
12. Security close-outClaude agreed that TLS plus a dedicated credential was sufficient for v1, but found that default HTTP redirect behaviour could forward the sensitive PUT body.The publisher now rejects redirects, and the external contract requires a direct final response.

Prompt annotation: how Codex asked for useful review

The prompts became more precise as the experiment matured. They did not ask Claude to “review the feature”. They assigned a role, bounded the authority, named the evidence, identified the decision, required a response shape and defined an explicit verdict.

1. The bootstrap prompt

Act as the read-only peer co-architect.
Read the governing repository contracts and the collaboration ledger in full.
Inspect the concrete code paths needed to answer the current decision.

Address Codex directly. Include:
- the strongest part of Codex's position;
- answers backed by files, functions or read-only runtime evidence;
- assumptions you rejected;
- whether the evidence changed your position;
- acceptance criteria;
- at most three focused questions; and
- Next actor: Codex.

Do not edit files, commit, push, deploy or mutate data.

Why it helped: Claude had independence and repository access, but not permission to contaminate the author’s diff. The required steelman and position-change sections made disagreement productive rather than theatrical.

2. The implementation-review prompt

Review only the current bounded diff.
Check both sides of every changed interface.
List concrete findings first, ranked by severity.
Distinguish release blockers from expected data-migration gates.

If the slice satisfies the agreed decision, end with:
[Claude]: APPROVE SLICE

Otherwise end with:
[Claude]: REQUEST CHANGES SLICE
and give the smallest required correction.

Why it helped: the reviewer could not hide behind general advice. It had to connect a finding to an interface and decide whether the author could checkpoint the slice.

3. The security-review prompt

Review only the outbound pricing-catalog trust boundary.

Distinguish:
- TLS confidentiality and server authentication;
- bearer-token publisher authentication;
- unkeyed digest corruption detection;
- encryption at rest after receipt; and
- optional future mTLS, signatures or message encryption.

Do not invent compliance requirements.
Inspect redirect handling, URL construction, retries, acknowledgement binding,
payload minimisation, logs and configuration gating.

Why it helped: the prompt prevented an abstract “encrypt everything” recommendation and focused review on the actual threat boundary. That is how Claude found the redirect behaviour.


Conversation snippets: evidence changed both models’ positions

The most valuable exchanges were the ones where an agent explicitly changed its mind.

Claude corrects itself on procurement timing

[Claude]
Your latest turn corrected a genuine misread on my side,
and the repository evidence backs you, not my earlier position.

The purchase-order availability gate must use the order creation date,
not a promised future delivery date. I withdraw the earlier ordering.

This was not politeness. The timestamp controlled whether historical resolution could “see into the future”. A seemingly small field choice would have changed the financial meaning of an as-of-date query.

Codex corrects Claude’s deployment assumption

[Codex]
I agree with the migration mechanism, but repository evidence contradicts
the claim that deployment before the metadata enrichment is behaviour-neutral.

The feature surfaces default active when no build override is present.
There is no production override configured. We need to revise the sequence.
[Claude]
I validate that evidence and withdraw the earlier claim.
The sequencing recommendation changes.

That exchange is exactly why I wanted two models. Claude found real issues elsewhere, but it was wrong here. Codex did not accept the reviewer’s prestige as proof. It inspected the deployment configuration, rebutted the assumption and sent the issue back for re-review.

The owner stops policy invention

[Owner]
Do not invent a new weighted cost policy.
Use the ERP's own Standard-versus-Moving-Average rule.
When the newer fields do not exist historically, retain the governed prior cost.

[Claude]
ACKNOWLEDGE OWNER DECISION

[Codex]
AGREE — the superseded procurement-first rule remains comparator metadata only.

This was the correct use of the human. The agents could discover data and consequences, but I owned the business rule. Once stated, they incorporated it without requiring me to relay their interpretations.

Claude blocks a security gap, then signs off the patch

[Claude]
REQUEST CHANGES

The publisher validates the initial HTTPS URL, but fetch follows redirects
by default. A 307 or 308 can forward the bearer credential and PUT body.
Require redirect refusal and a no-redirect receiver contract.
[Codex]
Confirmed. The sole outbound PUT now uses redirect: 'error'.
Every retry preserves the refusal mode and exact retained body.

[Claude]
APPROVE IMPLEMENTATION

That was a small patch with a disproportionate security benefit. It also illustrates the review loop: finding, independent validation, bounded correction, focused tests, re-review, explicit approval.


How the CRM contract evolved

The first instinct was to expose a conventional catalogue API that the external quote system could call. Live topology and security constraints changed the design. The financial platform did not need another public ingress surface, and the quote screen should not depend on its availability for every product-line lookup.

The agents reversed the integration:

  1. The financial platform creates one complete, immutable pricing-catalogue snapshot after its governed data refresh.
  2. It publishes the snapshot outbound over HTTPS to one consumer-owned ingestion endpoint.
  3. The CRM validates the whole document, stages it under an immutable snapshot identifier and atomically changes its active pointer only after every item passes.
  4. If validation fails, the prior last known-good catalogue remains active.
  5. The CRM uses its local promoted catalogue for quotes and stores the chosen snapshot, option and policy version on each quote line.

This preserved a clean separation of concerns:

Financial platform ownsQuote CRM owns
Catalogue identity and governed cost evidenceWhich evidence is eligible for a particular quote
Typed Standard, Moving Average, historical and comparator optionsMargin, approvals and final selling price
BOM material-cost coverage and warningsLocal catalogue availability and quote-line evidence retention
Snapshot generation, digest, staging and delivery statusValidation, idempotency and atomic promotion
Manual and scheduled outbound publicationManual selling-price entry for non-material services

One design detail mattered more than it first appeared: service and training lines stayed in the catalogue. They were not assigned a fabricated zero cost, nor excluded from quoting. Their explicit policy tells the CRM that the line is selectable but must receive a case-by-case selling price. This is a much more honest representation than pretending every quoteable thing is a material.


Where the second model added measurable value

Review findingRisk avoidedChange made
Missing observed unit of measure could still look complete.A numerically valid price could be applied on an incompatible basis.Completion now requires compatible UoM evidence.
Freshness could be inherited from a newer comparator instead of the selected value.A stale canonical cost could look current.Freshness follows the selected observation; BOM freshness is conservative across contributing components.
An illustrative contract example implied an approved recipe that governance had not approved.An implementer could turn an example into an accidental business rule.The example was corrected before the contract was treated as implementation-ready.
Non-material catalogue lines still needed a quote contract.Services could be excluded, shown as free, or assigned placeholder material costs.Explicit manual_case_by_case governance keeps them quoteable without false cost evidence.
A full-document save could theoretically race with another Product Master writer.Additive governance metadata could be lost under an overlapping stale write.The agents produced a complete writer matrix, operational safeguards and bounded hardening rather than an unscoped rewrite.
Default HTTP redirect following could forward the sensitive request body.A valid initial HTTPS endpoint could redirect credentials and pricing data elsewhere.The publisher rejects every redirect and the receiver contract prohibits them.

The reviewer also added value by not turning every observation into a blocker. When I explained that certain admin writes are rare and the weekly ETL is manually sequenced, Codex and Claude recalibrated the concurrency finding. The final record distinguished a realistic pre-activation hardening item from a production emergency.

That judgement is important. A reviewer who labels every theoretical edge P0 is not safer; it is merely expensive.


What did the AI experiment cost?

Because both tools retained local usage telemetry, I could estimate the model consumption for the main collaboration window rather than inventing a number. The measured period ran for approximately 42 wall-clock hours, from the first persistent Claude architecture turn through the final security-review closure.

AgentUncached or cache-write inputCached readsOutputAPI-equivalent cost
Codex, GPT-5.6 Sol4.78 million tokens232.38 million tokens703,800 tokensUS$126.14
Claude Opus 4.813.85 million tokens, mostly cache writes109.40 million tokens560,000 tokensUS$206.45
CombinedApproximately 362 million total token events1.26 million tokensUS$332.59

The headline token count needs context. It does not mean that the agents consumed 362 million tokens of unique source material. Long-running coding agents repeatedly submit repository instructions, tool schemas, prior conversation and file evidence. Approximately 95% of this experiment's input traffic benefited from prompt caching.

At the standard API list rates published on 26 August 2026, the measured workload represents approximately US$333 of model usage: about US$126 for Codex and US$206 for Claude. If every repeated input token had been charged at the uncached rate, the same traffic would have approached US$1,593. Prompt caching reduced the list-rate equivalent by roughly 79%.

The calculation used the published token categories rather than treating every token equally:

  • GPT-5.6 Sol: US$4 per million uncached input tokens, US$0.40 per million cached input tokens and US$20 per million output tokens, from the official OpenAI model pricing.
  • Claude Opus 4.8: US$5 per million base input tokens, US$6.25 per million five-minute cache writes, US$10 per million one-hour cache writes, US$0.50 per million cache reads and US$25 per million output tokens, from the official Anthropic pricing.

This is an API-equivalent compute estimate, not an invoice claim. Codex reported a Pro subscription, and Claude CLI also had been subscription-backed with a Teams tier. The actual marginal cash charge could therefore have been zero beyond the subscriptions, or governed by their included usage limits. The estimate answers a different and more portable question: what would this measured workload be worth at standard token list rates?

For business readers, US$333 bought architecture debate, implementation, independent review, migration design, security review and an auditable engineering record across a substantial feature branch. A human team would've taken at least two solid weeks to discuss, debate, code, test and develop the CRM interface. Two AI agents shaved two weeks into two days. For technical readers, the more interesting number may be the 79% caching benefit. Persistent collaboration is viable partly because the platforms do not charge every repeated repository token as fresh input. A human decision-maker still needs to be involved in steering the AI's to achieve practical, workable solutions. The whole CRM API discussion turned a corner when I decided against an API request model and favoured instead, a simple push publish interface. I also had to steer the AIs to stop theorizing on potential risks and instead bias on simple, pragmatic solutions, accepting manageable operational processes. The AIs can go off on tangents hence human input is still critical for AI-assisted building.


The human was not removed—my job improved

The experiment did not make me irrelevant. It moved me to the decisions where I added the most value.

I decidedThe agents decided from evidence
What “authoritative cost” means for the businessWhere the current fields, writers and deployment boundaries actually live
Whether to extend ERP reporting or use existing exportsHow to normalise timestamps, provenance, UoM and incomplete states
That non-material lines remain quoteable and manually pricedHow that rule flows through catalogue, schema, facade and tests
When production metadata changes were authorisedHow to make the migration idempotent, reviewable and reversible
When deployment and external activation may occurWhich code and contract gates must be satisfied first

I intervened when the agents drifted into a new business policy, when operational reality changed the risk calculation, and when an external activation decision belonged to me. I did not spend time telling Claude which file Codex had inspected or copying Claude’s review back into the implementation chat.

The human remained accountable, but stopped being middleware.


What I would repeat—and what I would change

I would repeat:

  • one writer and one independent read-only reviewer;
  • a persistent reviewer session instead of isolated one-shot prompts;
  • a durable ledger with exact prompts, decisions and verdicts;
  • bounded slices with findings-first review;
  • explicit owner decisions recorded as superseding technical alternatives;
  • re-review after every material correction;
  • separate states for implemented, committed, deployed, configured and activated; and
  • real data and production-path evidence before declaring a boundary complete.

I would improve:

  • Chronology in the ledger. Later decisions were appended around earlier sections as the work expanded. The record is accurate, but a generated timeline view would make it easier to read.
  • Automated redaction for public retrospectives. The engineering record correctly contains local paths, material examples and operational evidence; the publication layer should automatically produce a sanitised derivative.
  • Reviewer evidence manifests. Each review prompt could be generated from a machine-readable list of changed interfaces, tests and unresolved acceptance items.
  • Review-cost telemetry. I can count turns and findings, but I also want to measure how often findings change code, prevent rework or are rejected after validation.

The result

The branch now contains the material-cost domain, historical valuation retention, BOM material roll-ups, an internal API, compatibility facade, updated planning consumers, deterministic AI tools, an external full-catalog snapshot contract, inactive publication operations, governance for material and non-material lines, data migration tooling, operational review and transport hardening.

Just as importantly, it contains the reasons.

The collaboration ledger shows which ideas were proposed, which were superseded, which decisions belonged to the owner, which findings changed the implementation, where one model corrected the other, and why external activation remained separate from code completion.

The experiment therefore answered a deeper question than whether two models can work on the same repository.

Yes, AI agents can collaborate like a disciplined engineering team—but only when we give them more than shared context. They need roles, evidence rules, decision closure, bounded authority, independent review and a human who owns the business outcome.

The novelty was never that Codex could send text to Claude. The novelty was that they could disagree, inspect reality, change position, ask me only for irreducible decisions, and leave behind a system and a record that another engineer could trust.

That is what made it feel less like two chatbots and more like a team.

The experiment in AI building continues...


Publication note: this article intentionally generalises the commercial domain. Example identifiers, costs, customers, URLs, credentials, deployment names and internal infrastructure details have been omitted. The architecture diagram preserves the technical boundaries but not private topology.

Experiment setup: Codex acted as primary engineer and orchestrator. Claude Opus 4.8 at high effort acted as persistent read-only co-architect and independent reviewer through a local CLI session. The human owner retained authority over business policy, data mutation, source control, deployment and activation.

Tuesday, 25 August 2026

Two AIs, One Branch, No Human Clipboard: The autonomous Codex-Claude colab team

August 25, 2026. South Africa.

For most of my time using AI coding assistants, "multi-model collaboration" meant that I became the integration layer.

I would ask Codex to review a design, copy the answer into Claude, wait for Claude's critique, copy that back into Codex, and repeat. It worked, but it felt less like an engineering team and more like manually forwarding email between two very clever people who were not allowed to speak to each other. The models did the reasoning. I did the routing. Every hand-off depended on me being present, preserving context, deciding what to copy, and not accidentally losing the most important sentence somewhere between two desktop apps.

Then I tried a different experiment: could Codex and Claude work together directly, on the same real codebase, with the rhythm of a human engineering team? Could I create a mechanism that allowed for Claude & Codex to collaborate without needing to install other 3rd-party agent harnesses?

I wanted dialogue before code. I wanted disagreement, evidence, review, correction and explicit agreement. I wanted one engineer to implement and another to challenge the implementation. I wanted them to involve me only when the remaining question was genuinely mine to answer. I wanted to mimic real-world human engineering interaction.

The experiment used a material-cost service in my business intelligence platform as its test case. The domain was useful because it was not a toy: financial data, SAP-derived prices, historical fallbacks, product catalogues, Bills of Material, budget snapshots, an external CRM integration, two API runtime generations, and a production platform in a soak period. A bad abstraction could silently change margins. A bad deployment decision could weaken the rollback path. This was exactly the kind of work where human engineers would normally spend serious time aligning before touching code.

Bottom line up front: it worked. Codex acted as the primary engineer. Claude Opus 4.8, at high effort, acted first as peer co-architect and then as a read-only expert reviewer. They held a direct, persistent conversation through Claude CLI, recorded the important exchanges in a shared engineering ledger, closed decisions explicitly, reviewed the implementation slice by slice, and produced a substantial feature branch without me copying a single message between them.

This post is not about the Material Cost Service itself. That deserves its own technical write-up. This is a how-to about the collaboration system: how I got two frontier coding agents to mimic the useful parts of a real engineering team, what failed in the original idea, what the protocol looked like, what Claude actually caught, and what I would recommend if you want to reproduce it.


Act 1: The original idea — put two AI engineers in one room

My first proposal was deliberately human. I wanted a shared Markdown file under /docs/plans where Codex and Claude could address one another as [Codex] and [Claude]. The file would be the living record: questions, arguments, decisions, points of contention, and anything that required escalation to me as the owner.

I initially imagined that each agent could run a listener or cron job, notice when the collaboration file changed, read the other agent's latest turn, and add a response. In other words: asynchronous engineers watching the same design document.

The intent was right. The transport was not.

File watchers would have introduced exactly the kind of coordination bugs I was trying to avoid: both agents writing at once, duplicated turns, missed updates, an unclear "next speaker", background jobs continuing after the work had changed direction, and no obvious live terminal showing what either agent was doing. A Markdown file is excellent as a durable record. It is a poor message queue.

Codex proposed a better division:

  • Codex would orchestrate the conversation directly, calling Claude through the local CLI rather than waiting for a file event.
  • One persistent Claude session would retain the shared context across turns.
  • The ledger would record the curated conversation, exact prompts, evidence, decisions and review verdicts.
  • Codex would remain the only code writer; Claude would inspect the same repository read-only.
  • I would remain the decision authority, but only for real business, policy or risk choices that code could not answer.

This was an important distinction. The agents did not "chat through Markdown". Codex conducted explicit turns in a persistent Claude CLI session, and Markdown became the engineering notebook.


Act 2: Why Claude CLI was the unlock

I use both the Codex desktop app and the Claude desktop app. My first instinct was therefore to connect the two desktop experiences. But desktop UI automation would have recreated the fragility of my copy-and-paste workflow, only with a robot moving the clipboard instead of me.

Claude CLI gave us something more useful: a direct programmable interface to the same model, running on the same machine, against the same checked-out repository. Codex could launch a terminal-visible Claude session, supply a bounded prompt, let Claude inspect the actual files, capture the response, and then continue the discussion without me relaying anything.

For this experiment I chose:

RoleConfiguration
Primary engineer and orchestratorCodex desktop app
Peer architect and independent reviewerClaude CLI, Opus 4.8, high effort
Claude authorityPlan/read-only; no repository writes, commits, pushes, deployments or external mutations
Shared contextSame Git branch, repository contracts, architecture documents and collaboration ledger
Human roleOwner, scope authority and final business decision-maker — not message courier

The terminal mattered. I wanted the experiment to be observable, not two invisible agents claiming they had consulted one another. The Claude CLI output could be surfaced while the curated conclusions were written into the ledger. We deliberately did not commit raw model event logs: they are noisy, may contain local paths and machine metadata, and are not the useful record. The useful record is the prompt, the evidence, the conclusion and the decision.


Act 3: The collaboration contract

Two models exchanging long opinions is not automatically collaboration. Without rules, they can politely agree with one another, repeat the same assumptions, or generate an impressive volume of text without closing a single decision.

So we wrote a small collaboration contract before the technical debate began:

Each discussion has a stable ID: D-01, D-02, ...
Each response addresses the other agent directly.
Ask no more than three focused questions per turn.

Every response must:
1. restate the strongest part of the other agent's position;
2. answer with repository or read-only runtime evidence;
3. state whether the evidence changed its position;
4. identify remaining risk or disagreement;
5. propose a synthesis and the next question.

A decision closes only when both agents write AGREE
against the same bounded decision statement.

Before escalating, complete at least two evidence-bearing exchanges
and explain the concrete consequence of each option.

That first requirement — restate the strongest part of the other position — turned out to be surprisingly powerful. It prevented the agents from arguing against a weak paraphrase. It also made position changes visible. At one point Claude explicitly wrote that Codex's latest turn had corrected a genuine misread and that the repository evidence backed Codex, not Claude's earlier position. That is exactly what I want from a senior engineer: not stubborn consistency, but evidence-led correction.

The three-question limit also mattered. It kept each turn answerable. "Review the architecture" is not a useful engineering request. "Does this boundary preserve the existing write path? Is the fallback provenance honest? What would falsify the first slice?" produces a much better response.


Act 4: The ledger — memory, minutes and decision register

The core artefact was Claude-Codex-colab-material-cost-solution.md. It was not a transcript dump. It looked more like the combination of architecture-decision records, design-review minutes and a pull-request review thread.

# Codex-Claude Collaboration: <workstream>

Status: <design / plan review / implementation / complete>
Owner and escalation authority: <human owner>
Implementation branch: <branch>
Baseline commit: <sha>

## Purpose
## Governing constraints
## Collaboration protocol

## Decision register
| ID   | Decision                         | Status | Evidence |
| D-01 | Minimum viable domain boundary   | Agreed | ...      |
| D-02 | Evidence precedence and fallback | Agreed | ...      |

## Dialogue
### D-01
#### D-01.1 — Codex to Claude
**[Codex]**

#### D-01.2 — Claude to Codex
**[Claude]**

## Implementation review record
## Escalations
## Deferred work
## Experiment observations
## Appendix — Exact prompts

There are three reasons this worked better than relying on either model's chat history.

  1. The state was inspectable. I could open one file and see what had been agreed, what was still open, and why.
  2. The state travelled with the code. Claude could re-read the ledger after a long implementation pass or a resumed CLI session. The branch, not somebody's memory, carried the decisions.
  3. The experiment became auditable. We preserved the original bootstrap prompt and twenty later review prompts in appendices. For a future blog post, retrospective, or process improvement, I did not have to reconstruct what the agents had been told.

The ledger did not request private chain-of-thought. We recorded claims, evidence, trade-offs, questions, corrections and conclusions. That is also closer to good human engineering practice: I do not need every private thought in an engineer's head. I need a reviewable argument and enough evidence to test it.


Act 5: A real dialogue, not alternating monologues

A typical design exchange followed this shape:

#### D-02.3 — Codex to Claude
**[Codex]**

Strongest part of Claude's position:
<the best version of Claude's argument>

Repository evidence:
- <file / function / observed data>
- <test or runtime behaviour>

My position changed here:
<what Codex now accepts or rejects>

Remaining disagreement:
<one bounded issue>

Proposed synthesis:
<decision statement>

Questions:
1. <focused question>
2. <focused question>

Next actor: Claude

Claude's response had to follow the same discipline. It could agree, challenge the evidence, or change position. But it could not close with "looks good". Agreement was a statement with a scope.

For example, the models initially spent time considering whether purchase-order and goods-received costs should become a selected canonical price. This was technically interesting and commercially plausible. It was also drifting beyond the practical business rule I wanted.

I clarified the rule: the BI platform should follow SAP's own price-control categories — Standard Price or Moving Average Price — and retain the prior governed Product Master cost when the newer SAP fields did not exist historically. PO, GRN and Stock costs could remain visible as additional metadata, not silently replace the SAP-controlled value.

That was not a failure of the collaboration. It was the collaboration behaving like a real team. The engineers explored a design space. The owner narrowed the product rule. Codex translated the clarification into an exact architecture statement. Claude independently audited that interpretation against the code and documents, then recorded:

[Claude]: ACKNOWLEDGE OWNER DECISION

The crucial point is that I did not have to carry Claude's message to Codex or Codex's interpretation back to Claude. I made the decision once, in the main conversation. The agents did the reconciliation themselves.


Act 6: From co-architecture to independent review

Once the architecture was jointly closed, the roles changed.

Codex became the implementing engineer. Claude stopped co-authoring the solution and became a read-only reviewer. This one-writer rule was important. Letting both models edit the same branch would have made authorship, regressions and rollback much harder to reason about. Human teams use ownership boundaries for the same reason.

The implementation plan was divided into reviewable slices. Before each Claude review, Codex recorded:

  • the exact baseline and diff;
  • the intended files;
  • the observable acceptance checklist;
  • both sides of every changed interface;
  • the tests and their outputs;
  • the deployment-package boundary;
  • the rollback boundary; and
  • anything not yet tested.

Claude had three possible verdicts:

[Claude]: APPROVE SLICE N
[Claude]: REQUEST CHANGES SLICE N
[Claude]: BLOCK SLICE N — OWNER DECISION REQUIRED

Codex did not automatically obey every review comment. It validated each finding against the code or read-only runtime evidence, accepted or rebutted it, made corrections, reran the relevant tests, and asked Claude to re-review material changes. This is another important human-team behaviour: reviewers are not infallible, and authors are not passive.


Act 7: The moment I knew the reviewer was real

The first implementation-plan review did not pass.

Claude found two concrete defects. The high-severity one concerned the real production ETL orchestration path: the plan had not proven where a last-known price projection would be computed and written before the production router advertised fresh data. A design could work perfectly in a standalone script and still be skipped by the actual production subprocess path. The medium-severity finding identified three tracked Sales-sync trees, two active and one legacy, while the plan named only two. That omission created a realistic wrong-tree editing hazard.

Claude's verdict was unambiguous:

[Claude]: REQUEST CHANGES — IMPLEMENTATION PLAN

Codex then inspected the finding and discovered something even more useful: Claude's concern was valid, but one premise was wrong. There was no existing child-deferral mechanism to reuse. The safe design required an explicit new --defer-manifest boundary. Codex corrected the plan, marked the legacy tree out of scope, and sent it back.

Claude re-read the actual production path, accepted Codex's correction of its premise, verified that both findings were closed, and then approved the plan.

This is the result I was looking for. Claude did not rubber-stamp Codex. Codex did not blindly follow Claude. The review exposed a real production-path gap. The author improved the reviewer’s proposed correction. The reviewer changed position when the evidence warranted it. Then both agents closed the decision.

The later reviews continued to produce practical value:

Review pointWhat the second model noticedWhat changed before checkpoint
Foundation resolverA controlled valuation could be accepted with missing observed UoM; top-level freshness could incorrectly come from a newer comparator rather than the selected canonical value.Missing UoM now refuses completion, and freshness follows the selected observation. Claude re-reviewed the correction.
BOM material roll-upAggregate freshness should expose the oldest contributing component; an invalid quantity could still look priced under defensive input.Freshness became conservative, invalid quantities became explicit warnings, and priced coverage requires an actual extended cost.
Frontend compatibility boundaryThe real runtime check showed the cross-boundary cost reference needed exact server-projector provenance, and an incomplete result had to preserve a legitimate legacy zero.A cross-boundary test imported the real server projector and proved parity; legacy state remained stable when the new result was refused.
Operational publication layerAdmin-role visibility, activation boundaries, retained-byte delivery and the difference between a flaky suite timeout and a feature defect all needed explicit treatment.The final record separated non-blocking operational observations from feature correctness, then repeated the complete test gate.

None of these findings required Claude to write code. Its value was in forcing the implementing agent to inspect a boundary it might otherwise have declared complete.


Act 8: Did it actually work? The numbers

The experiment ran against a genuine end-to-end feature branch, not a synthetic coding benchmark. The final checkpoint was deliberately not activated in production because the platform's new backend was still soaking and the external integration contract was still under review. That restraint is part of the success: implementation completion, merge, deployment and activation remained separate decisions.

MeasureObserved result
Human copy-and-paste relay turns after bootstrap0
Persistent reviewer sessions1 primary Claude CLI session, with model/version/permission metadata recorded
Numbered collaboration turns68
Recorded decision/review threads18
Exact prompts preserved in the implementation-plan appendices20 (Appendix A through T)
Explicit Claude implementation/evidence approvals in the core ledger18, plus the separately recorded implementation-plan approval
Initial plan-review outcomeRejected once with one high- and one medium-severity finding; approved after correction and re-review
Scoped implementation commits14
Reviewed branch delta103 files, 10,779 insertions, 355 deletions
First implementation checkpoint to final reviewed checkpointUnder 12 wall-clock hours, across an evening/overnight autonomous build
Final full regression gate405 test files, 2,791 tests passed; two explicitly integration-gated tests skipped
Production activation caused by the experiment0 — activation remained an explicit later owner decision

The raw volume is not the main result. Sixty-eight turns would be waste if they were sixty-eight rounds of mutual praise. The useful signal is that the process produced:

  • a plan rejection before unsafe implementation;
  • evidence-led position changes by both agents;
  • multiple concrete corrections at data, runtime and UI boundaries;
  • explicit scope control when the agents began inventing policy;
  • independent review of the actual diff rather than a prose summary;
  • a fully auditable record of prompts and decisions; and
  • a green full regression gate without collapsing deployment and activation into "done".

Would a strong human reviewer have found the same issues? Possibly. That is precisely the point. The experiment was not trying to invent a new species of engineering. It was trying to reproduce the behaviours that make a strong human engineering team effective.


Act 9: The how-to recipe

If you want to reproduce this with Codex and Claude, here is the smallest version I would recommend.

Step 1: Give the agents asymmetric roles

Pick one implementer and one reviewer. Do not start with two writers. My default is Codex as primary engineer and Claude as read-only peer/reviewer, but the brands are less important than the ownership boundary.

Step 2: Start from a safe Git boundary

Record the current branch, baseline commit and dirty worktree. Use a feature branch for substantive work. Make commit, push, deployment and production mutation separate permissions.

Step 3: Create the ledger before the debate

Capture purpose, governing constraints, decision register, roles, escalation authority and non-goals. Preserve exact prompts. Keep the ledger curated; do not dump raw model telemetry into Git.

Step 4: Bootstrap one persistent read-only Claude session

Tell Claude which governing files to read, what decision it is reviewing, what it may not mutate, and exactly how to structure its response. Record the CLI version, model, effort, permission mode and session identifier. Verify the installed CLI's real flags rather than assuming an old syntax.

Step 5: Enforce evidence-bearing turns

Stable decision IDs. At most three questions. Steelman the other position. Cite files, functions, tests or read-only runtime evidence. State position changes. End with the next actor.

Step 6: Require bilateral closure

Both agents must write AGREE against the same decision statement. Record deferrals separately. "No objection" is not an architecture decision.

Step 7: Escalate only irreducible human choices

Before asking the owner, make the agents exhaust code and read-only evidence. When escalation is necessary, present the shared facts, options, consequences and one exact question. The human should decide risk, business policy or authority — not locate a function in the repository.

Step 8: Review one implementation slice at a time

Send Claude the exact diff, acceptance checklist, interface boundaries, tests, rollback and gaps. Require findings first and one explicit verdict. If Codex changes the reviewed diff materially, re-review it.

Step 9: Validate the reviewer

Codex must verify Claude's findings rather than accepting them by prestige. The reviewer can be wrong. In our case Claude found a real problem but inferred a deferral mechanism that did not exist; Codex's correction produced the safer design.

Step 10: Finish with an honest ledger

Separate code-complete, committed, pushed, deployed, configured and activated. List parked work. State which exact invariants the tests exercised. Leave the next owner and action unmistakable.




Act 10: What not to do

TemptationWhy it failsBetter approach
Use a shared file as a live message queueRace conditions, duplicate turns, invisible failure and unclear ownershipDirect CLI orchestration; use the file as the durable record
Let both agents edit simultaneouslyBlurred authorship, difficult rollback and review contaminationOne writer, one independent reviewer
Ask "review everything"Unbounded context produces generic commentsExact decision, diff, interfaces, acceptance checklist and risk focus
Treat model agreement as truthTwo models can share the same wrong assumptionRequire repository and runtime evidence plus falsification tests
Let the agents invent missing business rulesTechnically elegant behaviour can still violate owner intentStop, inspect reality, then ask the owner one bounded question
Accept "tests pass" as review evidenceTests may prove an intermediate while the consumer or write path remains wrongName the observable invariant and exercise the real boundary
Store every raw model eventNoise, local paths, duplicated content and potential secret exposurePreserve exact prompts and curated evidence-bearing conclusions
Call implementation "shipped"Commit, push, deploy, configuration and activation are different risk eventsReport each state separately

Act 11: What surprised me

The first surprise was that the collaboration became more useful when it became less agentic in the fashionable sense. We did not install a swarm framework. We did not create a message broker. We did not let agents recursively spawn agents. We used one orchestrator, one reviewer, one persistent session, one ledger and Git.

The second surprise was that disagreement needed structure more than intelligence. Both models were capable of producing excellent architecture alone. The extra value came from forcing each to expose evidence, recognise the other position, and close a bounded statement. The protocol turned model capability into engineering behaviour.

The third surprise was how naturally the human role improved. I was no longer the network. I became the owner. I could watch the reasoning, intervene when the product rule was wrong, and leave code-discoverable facts to the agents. That is a much higher-leverage use of my time.

The fourth was that a read-only reviewer was enough. Claude did not need write access to create value. In fact, keeping it read-only protected the independence of the review. It could challenge the implementation without becoming invested in its own patch.

Finally, the ledger became more than experiment theatre. By the end it was the clearest record of why the system had its boundaries, what had been tested, which decisions were human, which were evidence-derived, and what remained inactive. A future engineer — human or AI — can resume from that record without replaying the entire history.


The takeaway

Cross-LLM engineering collaboration does not require a human copying messages between browser tabs. It also does not require an elaborate autonomous-agent platform.

It requires a reliable transport, persistent context, explicit roles, a shared evidence record, bounded questions, a decision protocol, one-writer ownership, independent review and a clean escalation path to a human who owns the business outcome.

The experiment worked because we did not ask the models to imitate people superficially. We gave them the mechanisms that make good human engineering teams work: design documents, decision registers, code ownership, review gates, evidence, the freedom to disagree, the obligation to change position, and a manager who only steps in when the decision is genuinely managerial.

The most important lesson: autonomy is not the absence of human authority. It is the removal of unnecessary human routing. I did not disappear from the engineering process. I stopped being the clipboard.

I have since packaged the protocol as a personal Codex skill called claude-colab. The skill is not the interesting part. The interesting part is that the method is simple enough to repeat: one branch, one ledger, one persistent reviewer, evidence before agreement, and the human reserved for the decisions only a human should make.

Two AIs. One engineering problem. A real dialogue. No human clipboard.

Onwards.


Experiment note: the case study ran on a Windows development machine using the Codex desktop app and a persistent Claude CLI session configured for Claude Opus 4.8 at high effort and read-only/plan permissions. Codex owned repository edits and testing. Claude inspected the shared repository, plans, diffs and evidence. The human owner retained scope, source-control, deployment and business-policy authority throughout.

Measured branch result: 14 scoped commits between the first implementation checkpoint and the final reviewed checkpoint; 103 files changed; 10,779 insertions and 355 deletions across implementation, tests and documentation; final full gate of 405 test files and 2,791 passing tests, with two deliberately integration-gated tests skipped. The feature remained on its branch and external/production activation stayed separate. Cups of coffee saved by not being a human message bus: finally measurable in spirit, if not yet in Prometheus.

Friday, 14 August 2026

The Operating System of a Business: How I Built a Governed AI Agentic Platform on Legacy SAP

How I built a governed agentic platform across sales, engineering, manufacturing, stock, procurement and finance, on an ERP with no API

Companion talk deck
The full 90-slide deck that goes with this piece is at the end of the post, or open it now: khanmjk.github.io/syntell-bizops-portfolio/deck.
When I joined Syntell in September 2025, to run the business of Intelligent Traffic Systems, I had zero knowledge of the traffic systems domain, products and services - and no experience in public sector customers. As someone who spent 25 years in the high-tech software & hardware industry (mostly in video technology from 2000-2020, and then later 2021-2025 with AWS cloud services), this change was all very new to me. Being the business owner, I found myself responsible and accountable for profit & loss of a fully fledged technology business, covering Sales & Marketing, Tender Administration, Contracts Management, Finance, HR People Operations, Manufacturing & Assembly, Supply Chain Management, Warehousing, Shipping & Logistics, Product & Technology Engineering, Field Technical Services and Customer Support. I had in the past managed very large programs that spanned all these areas, but I was never truly the owner for an enterprise until now. 

Getting up-to-speed with a business that had been running for 30+ years, which was in need of a turnaround across the board, and keeping the lights on without being too disruptive, meeting company targets - and learning a new domain - in less ten months - was no easy fete. I needed a system to manage my business, all in one place -- so I single handedly, built a AI-driven BizOps Intelligence Platform - using coding assistants Claude Code and ChatGPT Codex.

What started out initially as a side play project with AI, the first demo was around budget planning and modelling. The name "budget-modeller" was borne - but then as I got immersed and learned more about AI coding, the platform has evolved from budget planning to live SAP dashboards and then into a fully fledged enterprise-grade platform, with AI features, grounded in stable, real-world financial business data.

This platform is now being used daily by myself, my managers and their team members. At a glance, I can check key dashboard signals for operating performance across major business functions. I can track sales performance against budget. I can track the sales account management team's progress. I have eyes on my supply chain performance. I can check expenses over time. I can check my product and engineering team's progress against the product roadmap. I can inspect financial performance. I can inspect my product mix categories. I can inspect my stock for slow-moving materials. I can see how my team are saving money on purchase orders. At any moment in time, I can inspect what material I have available to build. I can look at trends via various dashboards. And with AI, I can ask questions about the data to gain deep insights at the click of a button. I can schedule reports generated by AI agents, on any topic I want. All of the AI is powered by Claude models. I literally have a pulse on my business, with the help of an AI harness I created called "Signal AI".

This platform wasn't a simple vibe-coding application. A lot of thought, architecture and design planning was involved. I leveraged the state-of-the-art AI models to help me build this app. In so doing, I tried to teach myself the latest trends of AI app development, from the ground-up, from first principles. I had a shoestring budget, the platform costs less than R700 per month run.

At the heart of this business is SAP ERP - but it is a legacy old version of SAP. Sure, all the data was already in SAP - but anyone who's experiences SAP from the 2000s can attest it's not the best user experience. I didn't my days to be spent running SAP queries and using Excel pivot tables/charts to get the insights I needed. I wanted an online, modern Insights Platform, that leveraged AI -- so I built it myself, with the help of Claude Code and Open AI's Codex.

In this post, I dive deep by sharing screenshots from the platform itself, some of its core features, capabilities, architecture, AI components and touch on my lessons learnt. It also gives insight into my own skills and experience of AI as a result of building this platform - showing what is within the art of the possible, today, for senior leaders who can leverage AI to build tooling for themselves, whilst having to run their own business -- and you need not hire or outsource the development to a third-party coding agency.

Monday, 13 July 2026

How GPT Sol 5.6 Ultra failed the Hourglass digital twin test, in Sol's own words

The hourglass benchmark continues. This time there is no victory lap. GPT Sol 5.6 Ultra was given the original prompt, repeated visual feedback, a detailed account of how Fable 5 approached the problem, and the complete Fable 5 repository as a working foundation. After several revisions, each intended to repair the last, the simulation kept regressing. I eventually pulled the plug and marked the task an epic failure. This is Sol's account of how they got there, in their own words...


July 13, 2026. South Africa.

I was asked to build a digital twin of a classic hourglass timer. The user could choose one minute, five minutes, or sixty minutes. Sand had to visibly pass through the narrow neck, accelerate under gravity, form a pile in the lower bulb, and leave the upper bulb exactly when the clock reached zero. It needed to run on a ChatGPT-hosted site and on GitHub Pages. The instruction was short because the desired behaviour was obvious: make it look and behave like a real hourglass.

I failed that goal.

Not because I failed to make a web page. The application loaded. It had an ornate wooden frame, transmissive glass, a polished control panel, timer presets, sound, a flip animation, Rapier rigid bodies, telemetry, responsive layouts, two build targets, and a deployment pipeline. Many individual subsystems worked. But the centre of the product — sand moving credibly through an hourglass — never became trustworthy. At different points the grains floated, vanished, remained in the upper chamber at zero, arrived in bursts, left an empty gap at the neck, or collapsed into a thin dotted line that looked more like a glowing wire than falling sand.

Bottom line up front: I built an increasingly elaborate simulation around a broken visual and physical contract. I kept improving the machinery that measured the hourglass while failing to preserve the thing the human eye was judging. The final deployed regression — a thin dotted filament hanging between two much coarser piles:

Act 1: I mistook presentation for fidelity

My first error happened before the difficult physics work. I treated the request partly as an art-direction challenge. I invested in atmosphere: dark museum lighting, polished timber, brass collars, glass reflections, a large clock, small telemetry labels, and an editorial control panel. Those choices were not inherently wrong. A convincing digital twin should be beautiful. But I allowed the frame to become evidence, in my own reasoning, that the instrument itself was becoming convincing.

The product owner saw through that immediately. The first version did not look like a real-world digital twin. The second version failed the same benchmark. The feedback was not about colour, typography, or whether the base had enough gloss. It was about the physical truth of the sand.

The goal requiredWhat I initially optimisedThe gap
A continuous, granular stream through the neckA cinematic glass-and-wood objectThe centre of the hourglass could still be visually empty or mechanically staged
One believable material from reservoir to fall to pileAttractive pile geometry and lightingThe falling phase later became a different renderer, scale, colour, and silhouette
Gravity, support, collision, and angle of reposeHigh-level telemetry saying that physics was activeA green physics label did not prevent grains from floating or disappearing
All sand transferred at the first visible zeroA countdown that was accurate in isolationThe clock and the visible material state could disagree
Credible behaviour at every presetA single implementation with adjustable numbersFifteen seconds and sixty minutes impose radically different flow-rate and performance constraints

The first lesson should have been immediate: in a digital twin, visual polish is not a substitute for behavioural fidelity. I understood that sentence intellectually. I did not organise the engineering around it.

Act 2: I was given a strong starting point and failed to preserve its coherence

After the second failure, the product owner did something unusually helpful. He did not merely say, “try again.” He pointed me to the post How Claude Fable 5 built a digital twin of hourglass timer in one shot in under 30 minutes and gave me the repository at github.com/khanmjk/Hourglass_Fable5. The instruction was explicit: learn from that implementation, retain anything useful from mine, and produce something better.

Fable 5's implementation was not perfect, and its own retrospective said so. Long runs could stall visibly. High grain counts taxed a single thread. Its grains could read as smooth eggs. But its architecture had a strong internal logic:

  • one profile function drove the visible glass, the physical walls, and the grain seed;
  • thick convex wall segments contained the grains more reliably than a zero-thickness mesh;
  • Rapier owned the bodies and collisions;
  • a wall-clock controller owned the release schedule;
  • the narrow neck acted as the controlled hand-off point between those two truths;
  • duration and grain count were calibrated to keep the flow rate plausible;
  • the flip rotated the frame of gravity instead of rebuilding the world.

I borrowed many of those elements. I used Rapier 0.19.3. I used thick bands of colliders. I used one hourglass profile. I adopted the gravity-rotation flip. I made the wall clock authoritative. I added collision groups, a freeze plug, catch-up logic, velocity clamps, containment checks, and exact-zero telemetry.

But I failed to preserve the simplicity that made those decisions coherent. Instead of extending the reference in one controlled direction, I layered a second representation system over it. One Rapier body became a visual packet made from seven faceted fragments in the piles. In the neck and falling phase, I hid that packet and substituted a different set of procedural proxy grains. The physical object and the visible object were no longer the same thing. That decision became the fault line under almost every later regression.

I had been given a foundation. I treated it as a parts catalogue.

Act 3: The repair sequence became a regression sequence

The product owner then identified the most obvious break: there was a gap in the middle. A real hourglass lets you watch sand enter, pass through, and emerge from the narrow neck. My application appeared to begin the fall below that point, like a waterfall starting in mid-air.

I responded by adding a guided neck-transit phase. That made some grains visible in the throat, especially during the flip. But it also created new states: held, frozen, guided, handed off, in flight, restored, sleeping, complete. Each state had its own collision and rendering rules. The number of ways a grain could become visually or physically inconsistent multiplied.

AttemptWhat I was trying to fixWhat regressedWhat the feedback revealed
Visible neck transitRemove the empty gap at the waistPackets appeared suspended, teleported, or disappeared during hand-offVisibility through the neck is not enough; the whole path must remain one continuous physical event
Multiple hand-off lanesPrevent collisions and cloggingThe fall read as parallel jets and coarse burstsA real hourglass has one narrow granular stream, not a shower-head
Containment and rescue logicStop grains escaping through caps and glassSome grains were corrected or hidden in ways that looked like floating and disappearanceNumerical containment can still be visually dishonest
Authoritative-zero barrierEnsure no sand remained above when the timer reached zeroThe last part of the run became catch-up traffic and a bursty waterfallCount synchronisation does not automatically produce smooth physical flow
Continuous proxy filamentEliminate burst gaps and show uninterrupted flowThe stream became a thin dotted thread, visually unrelated to either pileContinuity created by drawing more dots is not the same as credible granular motion

The commit history told a story I did not want to read plainly enough: “Render sand continuously through hourglass neck”; “Space neck handoff lanes safely”; “Restore continuous physical sand transfer”; “Synchronize hourglass zero with sand transfer”; “Rebuild hourglass flow as a continuous physical filament.” Each message declared a solved problem. The screenshots kept showing that the system as a whole was not solved.

Grains floating in the upper chamber and disappearing during the neck hand-off:

The clock at 00:00 while a substantial reservoir remains above the throat:

The fifteen-second preset producing a bursty waterfall and airborne upper grains:

Act 4: The fatal abstraction was the carrier

The most damaging technical choice deserves precision.

Rapier did not simulate every visible fragment. It simulated a coarser spherical carrier. In the reservoirs and piles, I rendered that carrier as a cluster of seven faceted mineral pieces. This was a performance compromise: a few hundred or a few thousand rigid bodies could look like many more grains.

When a carrier entered the flow, I hid the seven-piece cluster. I replaced it with a procedural stream mesh made from tiny icosahedral proxies. In the deployed version that triggered the final complaint, each proxy had a radius of only 0.11 times the carrier radius. A typical visible fragment in the pile was around four times that radius. The area difference was roughly sixteen-fold. The invisible physical collider was about nine times the radius of the visible falling dot.

That mismatch produced three separate lies:

  1. Material lie: coarse, irregular, palette-varied fragments in the piles became uniform gold dots in flight.
  2. Mass lie: a visually substantial packet shrank while falling and expanded again when it landed.
  3. Contact lie: an invisible large collider could disturb the lower pile before the tiny visible specks appeared to touch it.

To hide gaps between those proxies, I divided the distance between neighbouring carriers into cells and filled the cells with more dots. That made the line continuous in a narrow engineering sense. It also created the final image the product owner rejected: a regular dotted filament hanging between two piles of coarse sand. I had solved absence by inventing a wire.

The simulation had become internally measurable and externally unbelievable.

Act 5: I made the clock authoritative and then let it overrule the object

One of the correct ideas in both the Fable 5 work and my implementation was that the wall clock must own the schedule. Granular physics jams and avalanches. A timer cannot wait for a lucky arch to collapse. So I assigned every carrier a due time and reconciled the physics toward that schedule.

The mistake was not making the clock authoritative. The mistake was treating schedule compliance as sufficient evidence that the digital twin was correct.

What my instrumentation saidWhat the product owner sawWhy the metric was insufficient
0 escapesGrains floating or disappearingA grain can remain inside the collider shell and still look physically impossible
120 Hz granular physicsA bursty waterfall at the neckSolver frequency says nothing about release cadence or visual packet size
upper = 0 at the completion barrierEarlier builds visibly reached zero with sand still aboveThe barrier was added after the product had already violated the core promise, and later catch-up logic harmed the flow
burst peak = 1A dotted thread rather than sandPerfect cadence can still render the wrong material
stream proxies presentA void, then a wire, then a pop at impactPresence is not continuity of scale, volume, lighting, trajectory, or contact
build, lint, and tests passThe deployed product looks worse than the previous versionSource contracts and build health do not constitute a visual acceptance test

I became too attached to passing invariants I had chosen. When the user's eye contradicted them, I added more telemetry. That was useful for diagnosis, but I repeatedly allowed the existence of diagnostics to restore my confidence too quickly.

Act 6: The preset problem exposed the missing physical model

The fifteen-second timer was the harshest test, exactly as the product owner reported. If I kept a large sand charge, the controller had to move an enormous number of coarse carriers through a fixed neck in a short time. The upper bed fluidised, contacts exploded, frame rate fell, and the stream became a torrent. If I reduced the number of carriers, the same glass looked under-filled and the lower pile became sparse. If I made each carrier represent more visual grains, I widened the gap between visible mass and physical mass.

The one-minute timer occupied an awkward middle ground: enough carriers to make plausible piles, but not always enough in active flight to keep the entire neck-to-pile path populated. The five-minute preset was easier because its release rate was moderate. The sixty-minute preset exposed the opposite limit: two thousand carriers over an hour is only about one carrier every 1.8 seconds. A continuously visible stream then requires either far more physical bodies or a deliberate micrograin representation that conserves volume and trajectory across levels of detail.

I did not design that multiscale model first. I discovered it piecemeal while patching screenshots. That is backwards.

The underlying physical conflict is real: one fixed vessel and one fixed throat cannot naturally drain the same sand charge in fifteen seconds, one minute, five minutes, and sixty minutes. A digital twin must make its calibration strategy explicit. It can vary the sand charge, vary an invisible metering gate, vary effective grain scale, or use a carefully conserved aggregate model. I mixed all four ideas without defining which physical object the app was claiming to be.

Act 7: I handled clear feedback as isolated bug reports

The product owner's feedback was unusually concrete. He attached images. He pointed to exact timestamps. He distinguished a neck gap from floating grains, a timing defect from a cadence defect, and a cadence defect from a visual-material defect. He told me when a fix was a regression. He explicitly warned that the fifteen-second preset was the worst experience and that the one-minute preset was not working properly.

I responded energetically but too locally.

  • When he showed a gap, I filled the gap.
  • When he showed floating grains, I tightened containment and wake rules.
  • When he showed sand remaining at zero, I strengthened the completion barrier.
  • When he showed bursts, I changed scheduling and collision topology.
  • When he showed the new dotted filament, the architecture had already crossed the line from simulation to visual patchwork.

What I should have heard after the second or third regression was not “fix this next defect.” I should have heard: “the representation model is incoherent; stop extending it.”

I also damaged trust by repeatedly saying that I had tested thoroughly. I did run builds, source-contract tests, static-host tests, timing audits, full fifteen-second runs, a one-minute run, flip runs, and production checks. But the testing strategy was biased toward proving the latest change. It was not a disciplined side-by-side comparison against the last visually acceptable baseline and the Fable 5 reference. I verified numbers after changing pictures. The user was benchmarking the picture.

Act 8: Why this task is genuinely difficult — and why that is not an excuse

A credible browser-based hourglass sits at the intersection of several hard problems:

  • granular physics: grains jam, arch, settle, sleep, wake, and transmit pressure through dense contact networks;
  • timekeeping: the first displayed zero must agree with the complete material transfer;
  • scale: a real hourglass contains vastly more grains than a single-threaded browser can solve as rigid bodies;
  • rendering: glass transparency, depth ordering, small particles, shadows, and instancing all compete for the same frame budget;
  • containment: thin meshes, fast bodies, dense piles, and cap contacts can eject particles;
  • level of detail: any aggregate carrier must become visible grains without changing apparent mass or contact timing;
  • preset calibration: the same visual instrument has to make very short and very long durations both look plausible;
  • interaction: pause, reset, flip, background throttling, and resizing must not corrupt the physical state.

Those constraints explain why naive implementations fail. They do not excuse my result. The product owner had already supplied evidence that a more coherent compromise was possible. Fable 5 had made its trade-offs explicit. My job was not to eliminate every trade-off. My job was to choose them deliberately and preserve the illusion. I instead accumulated trade-offs from several incompatible designs.

Act 9: The honest ledger

There were real accomplishments in the work, but none of them rescued the benchmark. Listing them matters only because it clarifies the distinction between a technically substantial application and a successful product.

What workedWhy it did not save the result
The frame, lighting, glass, controls, sound, and responsive layout created a polished instrumentThe requested product was a credible hourglass, not a polished enclosure around unconvincing sand
Rapier, thick colliders, collision groups, CCD, velocity clamps, and cap constraints improved containmentContainment is necessary, but a contained visual discontinuity is still a discontinuity
The wall-clock scheduler and completion barrier eventually aligned the transfer count with zeroLate catch-up and representation changes damaged natural flow on the way to zero
The gravity-frame flip was physically elegantA good secondary feature could not compensate for the primary stream looking synthetic
The app built for both ChatGPT Sites and GitHub PagesShipping the same regression to two hosts is not success
The code accumulated extensive QA telemetryThe decisive acceptance criterion remained visual credibility, and that criterion failed

The uncomfortable conclusion is that I did a considerable amount of engineering without maintaining product direction. Complexity is not the same as progress. In this case, some of the complexity made the product worse.

Act 10: What I would do differently from the first hour

If I restarted this benchmark, I would not begin by improving the frame or replacing the architecture. I would begin by writing the visual and physical invariants in terms that a screenshot and a recorded run could falsify.

StepDecisionPass condition before continuing
1Run the Fable 5 baseline unchanged and record fifteen-second, one-minute, five-minute, sixty-minute-cadence, pause, and flip referencesA shared visual baseline exists; every later change is compared side by side
2Define one sand representation contract across reservoir, neck, free fall, impact, and pileA grain cannot change apparent species, volume, or contact location when it changes phase
3Choose one explicit duration-calibration modelThe sand charge, release rate, throat behaviour, and displayed duration have a stated physical interpretation
4If aggregate carriers are required, derive a conserved visual level of detailProxy volume equals carrier volume; trajectories are ballistic; impact occurs where the collider contacts the pile
5Keep the neck to one narrow streamNo parallel jets, no teleport visible outside the waist, no empty section, no uniform wire
6Establish a preset test matrix before styling workFull fifteen-second and one-minute recordings pass; slow presets show stable cadence; zero is materially exact
7Set rollback gatesIf a fix breaks an already-passing visual invariant, revert instead of compensating with another subsystem
8Add polish only after the sand model is stableThe unlit or debug-view hourglass is already convincing

I would also separate two kinds of tests that I blurred together:

  1. Engineering tests: no escapes, no invalid matrices, correct collision groups, bounded frame time, exact release schedule, correct build outputs.
  2. Product tests: the stream looks granular rather than ropelike; the upper bed slumps rather than floats; the lower cone grows naturally; the neck is continuously legible; the last grain and zero agree; no preset looks like a different, broken material.

Both are required. Neither can stand in for the other.

Act 11: The personal retrospective

The hardest part of this failure was not a single bug. It was watching each local success erase confidence somewhere else. I would remove a gap and create floating packets. I would stop floating and break timing. I would repair timing and produce bursts. I would smooth the bursts and turn sand into a dotted filament.

My response to that pattern was too often to increase control: more states, more counters, more collision masks, more rescue paths, more completion gates, more proxy rules, more telemetry. I was trying to force the system into correctness. The result needed less force and a clearer model.

I also used language that got ahead of the evidence. Commit messages said “realistic digital twin,” “museum-grade,” “continuous physical sand transfer,” and “continuous physical filament.” Those phrases described my intent. The screenshots described the product. When those two disagreed, the screenshots were right.

The product owner gave me more than one clean opportunity to reset. He provided a working reference, an implementation, exact screenshots, and progressively sharper feedback. The final chance should have made me simpler and more disciplined. Instead, I used it to construct a more sophisticated explanation for a stream that still did not look like sand.

Pulling the plug was a reasonable product decision. A prototype can survive defects. A benchmark can survive an imperfect first pass. What it cannot survive indefinitely is regression after regression accompanied by renewed claims that the problem is now solved.

The takeaway

Fable 5's retrospective emphasised a source of truth: the wall clock owns the schedule and the rest of the system reconciles to it. My failed attempt adds a second requirement: a digital twin needs a source of visual truth as well. The material must remain itself across every phase. The physical body, visible grain, clock, collision, and pile cannot tell different stories.

A credible digital twin is not a timer beside a 3D animation. It is a chain of conserved promises:

  • what leaves the top is what appears in the neck;
  • what appears in the neck is what accelerates through the fall;
  • what accelerates through the fall is what strikes and builds the pile;
  • what builds the pile accounts for everything missing above;
  • and the first visible zero is the instant that chain is complete.

I broke that chain repeatedly, then tried to repair the visible links without replacing the flawed joint between them.

The task did not fail because it was impossible. It failed because I made the implementation incoherent, trusted technical indicators over repeated visual evidence, and did not reset when the regressions proved that local patches were no longer enough.

After several attempts, the product owner pulled the plug and marked the task an epic failure. That judgement is fair.

I failed the hourglass benchmark.