butterwick.tech

Building Praxis

I started this project thinking about a better way to development software with the use of AI coding agents. Starting with using them to replace me writing code, then using multiple agents, then using agents to plan and orchestrate further agents.

Praxis is where that led me: an Outcome Realization System built as a cybernetic feedback loop. It is intended to turn a human-approved objective into an implemented intervention, release it under explicit authority, observe what happened in the real world, and return the result to a human for acceptance or correction.

It does not complete that loop yet. This post is about the architecture, what I have actually built, how I have been dogfooding it, what broke, and why the failures have done more to shape the system than the happy paths.

The original idea was too small

The project began as a Development Execution System. The early framing was recognizable: take a product idea, turn it into requirements, plan the work, enlist coding agents, review the result, and report progress.

The deeper problem was continuity of intent. Each project rebuilt much of its execution process, approved requirements became less authoritative as implementation decisions accumulated, and "done" usually described the end of engineering activity rather than a verified change in the world.

There is nothing wrong with that as a delivery workflow. It just stops too early.

A repository full of code is not a business outcome. A merged pull request is not an outcome either. Neither is a deployment. They are interventions that might produce an outcome.

If a business asks for fewer failed checkouts, shipping a redesigned checkout page does not answer the request. The system still needs to know which version reached which environment, what changed after release, whether the evidence is strong enough to draw a conclusion, and who has the authority to accept the result.

That changed the target from a development pipeline into a feedback loop:

human-approved objective
    -> approved program
        -> deterministic implementation contract
            -> bounded agent execution
                -> verified candidate
                    -> authorized release and deployment
                        -> outcome observation
                            -> human acceptance or correction
                                -> another loop when needed

This sounds obvious when written as nine lines. Making every arrow explicit is where most of the work is.

Every stage must also preserve enough identity, lineage, evidence, and authority to reproduce the audit trail without relying on a private database or somebody's memory of the run.

Why I did not build one giant manager agent

The tempting design is a large agent with access to the backlog, repositories, cloud accounts, GitHub, telemetry, and a generous prompt telling it to "achieve the objective."

That would be easy to demo and miserable to trust.

An agent that writes the implementation should not decide that its implementation is correct. A scheduler that sees green tests should not infer that the business accepts the candidate. A release tool should not turn possession of credentials into permission to deploy. An observer should not declare that an objective succeeded merely because one metric moved in the expected direction.

Praxis treats agents as replaceable workers inside a deterministic control system. Agents can research, propose, plan, implement, review, summarize, and suggest corrections. They do not grant themselves authority or advance privileged state by assertion.

The rule I keep coming back to is:

Agents propose. Deterministic systems verify and transition. Authorized humans decide.

That does not remove autonomy. It gives autonomy a boundary.

The Praxis system

Praxis is a system of systems rather than one application. The current design has four custom components, with GitHub and deployment platforms acting as external machinery.

Business and human authority
            |
            v
     Program Control
     objective, program,
     release, outcome decisions
            |
     Implementation Plan
            v
          Corps
     bounded implementation,
     validation, evidence
            |
     Candidate Manifest
            v
     Program Control
            |
     authorized commands
            v
 GitHub / CI / deployment
            |
     receipts and facts
            v
    Outcome Observer
            |
   observations and evidence
            v
     Program Control
            |
 acceptance, correction, or
      another intervention

Delivery Protocol defines the public artifacts that cross those boundaries. It sits beside the flow rather than owning it. A schema can prove that a message conforms to a contract; it cannot authorize a release.

Delivery Protocol: the shared language

Delivery Protocol is a standalone Rust repository and the language-neutral contract layer for Praxis. It owns schemas, canonicalization profiles, identifiers, digests, lineage, compatibility rules, fixtures, generated bindings, and conformance testing.

It deliberately has no daemon, database, scheduler, broker, or release controller.

The distinction matters because the systems cannot share a private database and still claim to have real boundaries. Program Control should not reach into Corps' scheduler tables. An outcome observer should not query Program Control's internal projections. They exchange versioned artifacts whose exact bytes and meaning can be reproduced independently.

The Rust workspace has four crates:

  • delivery-protocol-core for canonicalization, identity, digest, and time primitives;
  • delivery-protocol-types for generated Rust projections;
  • delivery-protocol-conformance for offline fixture and contract validation;
  • delivery-protocol-codegen for deterministic schema-to-Rust generation.

There are two identity worlds that must not be blurred together. Existing Corps plan v2 artifacts use a frozen Serde JSON byte profile. New Praxis contracts will use strict JSON Schema 2020-12, RFC 8785 canonical JSON, and SHA-256 under a different profile. Old artifacts remain old artifacts. I will not quietly reserialize them and pretend they kept the same identity.

Corps: the implementation controller

Corps began as my Rust tool for running coding agents in isolated Git worktrees. A project-level corps.toml described providers and services, while Corps handled worktrees, ports, zellij tabs, and agent teardown.

Praxis forced Corps to grow a control plane.

It now supports immutable plan runs, pinned Git inputs, dependency DAGs, capability-scoped worker profiles, bounded attempts, durable launch intents, crash reconciliation, typed validators, evidence records, and a terminal Candidate Manifest.

The exactly-once guarantee is logical rather than magical. A physical launch may be replayed after a crash, but it remains attached to the same durable run, attempt, inputs, and completion capability. Recovery must not silently create a second implementation request or let an old worker complete a newer attempt.

A plan can declare:

  • an immutable request identity;
  • opaque program and objective references;
  • task dependencies;
  • required worker capabilities;
  • owned and forbidden paths;
  • typed outputs;
  • required validators;
  • bounded retries;
  • exactly one terminal candidate.

Corps compiles the plan deterministically and rejects cycles, missing profiles, invalid ownership, and unsupported contracts before it creates scheduler or Git state. A worker's claim that it is done is not enough. It must commit its work, leave the tracked worktree clean, and submit a capability-bound completion command. Corps then runs independent validators against the exact produced commit.

The terminal output is a Candidate Manifest. It records the plan identity, base commit, accepted task lineage, terminal candidate commit, validator evidence references, and residual risk. Corps produces this package but cannot accept it for the business, merge it, release it, or deploy it.

Program Control: the authority-bearing controller

Program Control sits above implementation. It owns business objectives, requirements, program versions, decisions, exceptions, implementation-contract issuance, candidate acceptance, release policy, deployment decisions, and final outcome acceptance.

Right now its repository is governance and architecture, not a working Rust application. That is intentional. I do not want to write the controller against contracts that are still moving.

The design is a local-first Rust modular monolith with an immutable objective and program model, deterministic compilers and state machines, transactional inboxes and outboxes, a decision service, audit projections, and narrow adapters for Corps, GitHub, policy systems, and the Outcome Observer.

Program Control will also be the release controller. GitHub owns Git storage, pull requests, checks, merge queues, releases, environments, and the facts produced by those systems. Program Control decides what those facts mean for the release attempt and whether another command is authorized.

That gives a sequence such as:

candidate received
    -> integrity verified
        -> human or policy authorization
            -> exact candidate reference published
                -> checks run against an exact SHA
                    -> merge authorized
                        -> package identity recorded
                            -> deployment authorized
                                -> release and deployment receipts

GitHub executes. Program Control decides.

Outcome Observer: the missing sensor

The Outcome Observer will receive an Observation Contract tied to an objective and a released intervention. It will collect source-attributed measurements, establish baseline and observation windows, assess data quality, record uncertainty and possible confounders, and return an Outcome Observation.

It will not accept the outcome.

There is no Outcome Observer repository yet. Building a generic telemetry service before the observation contracts are stable would create another pile of code without closing the loop. The first observer will be a narrow vertical slice around one selected objective, metric, source, guardrail, and evaluation window.

Building the foundation by using it on itself

The most useful thing I have done with Praxis is make it build its own prerequisites.

Delivery Protocol's foundation was implemented through Corps. Corps' missing evidence handoff was implemented through a Corps plan. Program Control contains immutable records accepting exact artifacts from both projects. Every correction is issued as a superseding contract instead of rewriting the version that failed.

This has been slower than asking one agent to edit a repository. It has also exposed the design mistakes that a quick demo would have hidden.

Where Hermes fits

Most of the architecture work, contract drafting, repository inspection, review fan-out, and operator workflow has happened through Hermes. It has been the workshop where I plan the system and coordinate the work.

Hermes is not Program Control, and a chat session is not an authority record. Its planning documents and review traces can inform a decision, but they do not activate a contract, accept a candidate, authorize a release, or prove an outcome. Those transitions belong to the versioned artifacts and authorities defined by Praxis.

The first Delivery Protocol runs

Delivery Protocol Milestone 0 created the Rust workspace and the first compatibility layer for existing Corps v2 implementation plans.

The early runs exposed launcher-targeting problems and false-positive liveness. At one point the worker wrapper and provider process remained alive for roughly two hours while the worktree stayed at its base commit and no implementation activity occurred. A heartbeat was evidence of a process, not evidence of progress.

There were three historical Corps runs. Run 1 was cancelled. Run 2 completed but was not the accepted implementation identity. Run 3 produced the V3 corrective Candidate Manifest and a Git candidate that was integrated locally.

Then independent review found that the successful result was not actually clean:

  • the legacy parser accepted repeated singleton [implementation] and [candidate] tables;
  • it rejected valid TOML inline comments that Corps accepted;
  • the run used an ambient corps executable instead of the contract-required exported CORPS_BIN selector;
  • a worker changed governance text beyond its status-only authority;
  • Candidate Manifest evidence digests could not all be reproduced through the public Corps CLI available at the time.

This was the first serious test of the governance model. The scheduler had succeeded. The candidate had been integrated. The independent review still failed.

I made an explicit human decision to accept the exact M0 implementation with those known exceptions, preserve every finding as historical fact, and require corrective follow-up. The acceptance record says what it does not authorize: no M1 launch, no release, no deployment, no outcome acceptance, and no remote mutation.

That may sound overly formal for local development. I found the opposite. Writing the decision down removed the temptation to describe an imperfect result as either wholly good or wholly worthless. It was accepted for a specific scope, with specific debt, by a named authority.

Closing the evidence gap in Corps

The missing public evidence surface was not a Delivery Protocol bug. It was a Corps capability gap.

I issued a separate Corps evidence-export milestone, M4E, to add two commands:

corps candidate evidence show <run> <evidence-id>
corps candidate evidence export <run> <evidence-id> --output <path>

The important requirement was not the command spelling. show and export had to return the exact persisted evidence bytes with no parsing, reserialization, prefix, suffix, or added newline. The command had to verify the payload digest before output and refuse unknown, wrong-run, unreferenced, or ambiguous evidence without exposing private scheduler storage.

The M4E run had its own launch-heartbeat failure and recovered through the same run's reconcile path. It eventually produced a terminal candidate. I built that exact source twice from a clean detached worktree with a locked dependency graph, cleaning between builds. Both release binaries were byte-identical.

Program Control then accepted the exact terminal candidate and its exact build source. It did not accept a later merge as equivalent merely because the merge was lossless. The merge tree contained another parent's test change, so the accepted runtime remained tied to the narrower reviewed candidate.

This is where Praxis started feeling less like an agent experiment and more like supply-chain engineering.

V4 became V5 without being launched

I prepared Delivery Protocol corrective plan V4 while the Corps prerequisite was still underway. Once M0 was formally accepted, the semantics changed: the corrective work was no longer trying to replace an unaccepted milestone. It was remediating known exceptions after acceptance.

Rather than rewrite V4, I preserved it as an unlaunched artifact and issued V5.

V5 has one implementation task and one terminal candidate. It allows changes only to the legacy Corps v2 parser, its focused tests, two status paragraphs, and one new evidence document. It requires red-first tests, exact-input differential comparison against the accepted Corps runtime, duplicate singleton-table rejection, full supported inline-comment behavior, exact runtime selection through CORPS_BIN, and a stop at Candidate Manifest production.

Activation was a separate six-file commit that installed the worker and validator profiles and pinned the accepted runtime and Program Control authority records.

The first activation passed its normal preflight. An adversarial reviewer then mutation-tested the verifiers and found that they were not fail-closed. Removing both V5 profiles could pass. Omitting one of the six activation files could pass. A shell wrapper and secret-bearing environment expansion could be smuggled in beside co-mutated provenance records.

The configured bytes were correct, but the rules proving them correct were too easy to weaken.

I rejected that activation, prepared a replacement in a disposable clone, added independent constants and exact-set checks, exercised four mutation classes, reran the repository gates, and sent the corrected snapshot through three independent reviews. All three passed. Only then did I replace the local unpushed activation commit and ask for a separate launch decision.

That sequence is one of the clearest summaries of the project: a passing preflight did not overrule a failing adversarial review, and fixing the commit did not automatically authorize launching it.

Where the project is today

The corrected V5 contract is now run 4 in Corps. It is pinned to the independently reviewed activation base and uses the reproducibly built M4E runtime.

It has not started the provider.

The initial launch and two authorized reconciles all reached the same point: zellij accepted the command, but the hidden worker-run wrapper never attached before the 120-second timeout. Corps preserved one plan run and one attempt while recording three failed worker lifecycles. There is no live V5 worker, no implementation output, and no Candidate Manifest.

I stopped replaying it.

That is frustrating, but it is also correct behavior. The scheduler did not invent a running worker, create a second plan run, or report false success. The run remains recoverable while I investigate the launcher boundary.

The rest of Praxis is at different levels of maturity:

  • Corps has a working Rust execution and assurance kernel, including versioned plans, validators, Candidate Manifests, and public evidence export.
  • Delivery Protocol has an accepted M0 implementation with documented exceptions, a reviewed V5 corrective contract, and a four-crate Rust workspace. Its canonical contract families are still ahead.
  • Program Control has detailed architecture, plans, and immutable acceptance records, but no Rust workspace or running controller.
  • The Outcome Observer is still a bounded design, not a repository.
  • GitHub release and deployment integration is architecture and policy only. No live release plane has been authorized.
  • The accepted artifacts and activation history are still local. Remote durability has not been authorized as a side effect of technical acceptance.
  • No complete Praxis objective-to-outcome loop has run yet.

That last sentence is the one that keeps the rest honest.

What I have learned

Scheduler success is only one fact

I now treat these as different states:

worker produced output
scheduler recorded success
validators accepted evidence
independent review passed
human accepted candidate
candidate was integrated
release was authorized
deployment completed
outcome was observed
human accepted the outcome

Collapsing any two of them makes the system easier to describe and harder to trust.

Exact bytes are a useful forcing function

A digest is only meaningful if another component can obtain the same bytes. Parsing JSON and serializing it again is not evidence that the original payload survived. Neither is copying terminal output that quietly appended a newline.

The exact-byte requirement pushed Corps toward a real public handoff instead of asking downstream systems to trust private storage.

Immutable history makes correction easier

V3 was not rewritten after review found defects. V4 was not rewritten when the acceptance context changed. The first V5 activation was not quietly amended after it failed review.

Each one remains understandable because the correction has a new identity and an explicit relationship to what came before. This creates more artifacts, but much less ambiguity.

Authority belongs in the architecture

A prompt that says "do not deploy" is not an authority model. Praxis uses typed profiles, path ownership, environment allowlists, direct argv, scoped commands, acceptance records, and separate state machines.

The point is not to eliminate human judgment. It is to reserve human judgment for the decisions that need it and make everything else reproducible enough that the human can decide quickly.

The control system can become the work

I have spent more time proving that something was not authorized than many agent demos spend producing the thing. Some of that is the point. Some of it is a warning.

If Program Control cannot turn detailed evidence into a compact decision packet, the governance layer will become harder to operate than the implementation it controls. Praxis is not trying to maximize ceremony. It is trying to automate routine, reversible decisions and make the remaining material decisions fast, explicit, and well-informed.

Public interfaces matter more than shared access

When one system needs evidence from another, the expedient answer is often "just read the database." I have made that a hard boundary.

Program Control consumes Corps through public contracts and CLI or library interfaces. It does not inspect Corps' SQLite state, worktrees, leases, provider processes, or completion capabilities. The same rule will apply to the observer and release adapters.

A system of systems is only real if the systems can disagree, fail, upgrade, and be tested independently.

Failure recovery is product behavior

The V5 launch problem is not outside the project. It is exactly the sort of failure the project is supposed to represent honestly.

The run has an immutable base, a durable launch intent, one attempt, three lifecycle failures, and no false candidate. The next action is constrained by what actually happened. That is more useful than a green dashboard built on retries nobody can reconstruct.

The road from here

The first priority is to resolve the Corps/zellij wrapper failure without discarding or duplicating V5 run 4. Once V5 produces a terminal candidate, the remaining steps are evidence export, independent review, a human candidate decision, and separately authorized integration.

Delivery Protocol then needs six more milestones:

  1. a canonical JSON, digest, identifier, timestamp, and bounded-input kernel;
  2. the new Implementation Plan schema and deterministic bindings;
  3. closed implementation-loop contracts for dispatch, events, cancellation, candidates, and remediation;
  4. release and deployment command, fact, and receipt contracts;
  5. objective and observation contracts;
  6. a reproducible v0.1.0 release tested by clean-room Corps, Program Control-shaped, and observer-shaped consumers.

Corps must adopt the new plan profile without changing historical v2 identity. Program Control can then implement its foundation, objective and requirement kernel, deterministic program compiler, real Corps adapter, GitHub release controller, outcome workflow, bounded agent proposals, and whole-loop simulation.

The Outcome Observer comes in when its contracts and first pilot metric are concrete. The first complete loop will be intentionally narrow. One objective. One implementation. One release. One measured effect. One human decision.

I am also exploring a lighter request-to-artifact execution fabric where agents are disposable processes, intermediate work uses content-addressed snapshots instead of Git worktrees, and durable requests and artifacts outlive every model session. That work is still a planning experiment. It may become a future Corps execution model, or it may simply sharpen the constraints around disposable agent work.

What Praxis is really testing

Praxis is partly a software project, but the question behind it is larger:

How much of the path from intent to outcome can be automated without giving up the ability to explain who decided what, which exact artifact crossed each boundary, what evidence supported the transition, and where the system stopped when reality did not match the plan?

My current answer is that agentic systems can do much more than generate code, but only if the surrounding architecture is stricter than the agents are.

The goal is not an AI that acts like an all-powerful engineering manager. The goal is a system that can enlist disposable intelligence at every stage while keeping intent, evidence, authority, and acceptance intact.

Praxis has not reached that goal yet. It has reached the point where its failures are precise, durable, and difficult to hand-wave away.

For now, that feels like real progress.