Yesterday a Petri net picked up a goal, proposed four ways to move it forward, waited for me to choose one, wrote the spec, hired a headless Claude Opus 5, measured the result against twelve factors, reviewed the diff against its own spec, and merged the pull request. The interesting part is not that an agent wrote code. It is that the process around the agent is itself a running net, and every step left a token behind.
The problem: a coding agent is not a development process
A headless coding agent is very good at the middle of the job. Point it at a repository, give it a task, and it will read the code, edit it, run the tests and hand you a diff. That part is solved, and it got solved fast.
The rest of the job is not. There is no record of why this task and not another one. The acceptance criteria lived in a prompt that is now gone. Nobody measured whether the result still respects the architecture you agreed on three weeks ago. The next session starts with an empty head, so the twentieth task is as uninformed as the first. And a person who wants to steer has to be sitting in the terminal at the exact moment the agent asks, or not at all.
Teams normally solve this with process: a backlog, a spec, a review, a pipeline, a release. That process lives in five systems which do not know about each other, and none of which can act on its own. So we bolt an agent onto the side of it and hope the humans keep the pieces aligned.
Hermann is the other way around. The process is a running program, the coding agent is one step inside it, and both live in the same runtime. This article is about how that is put together, what it produced, and what is still missing.
What an Agentic-Net is, in one minute
Agentic-Nets is a governed, event-sourced operating runtime for persistent, evolving processes. Concretely: a model is a set of places that hold JSON tokens, and transitions that consume tokens from their input places, do one unit of work, and emit tokens to their output places. That is the whole vocabulary. A transition can be a deterministic mapping, an HTTP call, a shell command dispatched to an executor, a single bounded model call, a full agent with a tool budget, or a link between nets.
Two properties matter for everything that follows. First, every intermediate value is a token in a place, not a variable inside a process, so you can query the middle of a running workflow and see exactly what it knows. Second, every transition is deployed on its own: it has an inscription (a JSON document saying what it reads, what it does and where it writes), its own credentials, its own schedule and its own status. You can add one, stop one or replace one while the rest keeps running.
The consequence for an agent is the whole trick of this article. The agent is not the program. It is one transition. What it reads is a place. What it produces lands in a place. Whether it runs at all is a token somewhere else.
Hermann
Hermann is a persona spread over four nets in one session: setup, twelve-factor audit, iteration judgement and iteration build. Thirty-two lanes, ten scripts, one application on top. He owns one Spring Boot microservice at a time and builds it the twelve-factor way. He ships as a single package, so installing him is one call and he arrives with his nets, his scripts, his configuration and his application.
He is built on one rule about who is allowed to do what, and that rule is the reason the output can be trusted.
Deterministic scripts measure. Every number in Hermann’s world (test counts, startup seconds, factor scores, diff stats, image tags) is produced by a Python script on the executor and written into a place. No model is ever asked to count anything, which removes the single most common way an agent quietly lies to you.
Bounded model calls judge. Four lanes make judgements: which iterations to propose, what the spec should say, whether the diff matches the spec, and what the daily note says. Each is a one-shot call with an answer contract, a required set of fields with types. An answer that misses the contract is routed to an error place instead of the output place, so a malformed judgement can never pass itself off as a result.
The headless coding agent writes code, inside an approved spec, in one repository, with a least-privilege tool list. It never commits, never pushes and never talks to the git host. The pipeline does that, after it has run the build itself.
One iteration, end to end
Every iteration is the same loop, and it has exactly three points where a person decides something. The rest runs on its own, and stops safely wherever a person is needed, because waiting is just a token sitting in a place.
Walk it once. A trigger lands, either because you asked or because the previous merge started the next round. A script renders one brief: the goal, the accepted architecture decisions, the current state of the service, the last scorecard, the open risks, the specs already merged and the twelve factor cards. The model never reads the repository. It reads a document that a script wrote from measured facts.
The propose lane then answers in one of three modes, and it picks the mode itself:
- Choice. Three or four concrete next iterations, each small enough for one pull request, each with effort, risk and the factors it touches, exactly one marked as recommended. It prefers a failing factor, then a blocking risk, then an upgrade, then the most valuable missing capability of the goal.
- Interview. At most three precise questions, used when the next step depends on something only you know: a domain rule, an integration, a non-functional requirement.
- Goal definition. Used when the goal is still a placeholder. It asks for the goal in one line plus a paragraph, and offers three example goal statements to adapt.

You answer. A second script renders a second brief from your answer, and the spec lane writes the spec: user story, acceptance criteria as Given/When/Then statements, the API shape, the data change with its migration file name, the configuration as environment variables with defaults, the tests that prove each criterion, the factors touched, and an explicit out-of-scope list. You approve it, optionally with notes that go straight into the coder’s brief.
Then the coder lane spawns the headless agent on a fresh branch. When it stops, the pipeline runs the authoritative build, commits, pushes and opens the pull request. A separate lane proves that pull request in a clean worktree: build and tests, a container image tagged by the commit, the image started against a real Postgres on a private network, startup measured until the health endpoint answers, liveness and readiness probed, a SIGTERM sent and the shutdown timed, and a check that stdout is structured JSON. Only then does the review lane see a bounded diff and check it against the acceptance criteria. You merge, or send it back with notes and let the coder continue on the same branch.
The application on top of these nets has six sections: Setup, Goal and Architecture, Next iteration, Work, Quality and Journal. The important thing about it is what it is not. It is not a separate system with its own database. Every button writes a token into a place the nets already read, every panel is a query over a place, and an MCP client sees exactly the same stores and the same actions. The web page is a view, not the truth.
The twelve factors are twelve lanes
The twelve-factor methodology is the closest thing our industry has to a shared definition of a well-behaved service. It is also mostly enforced by memory and code review, which means it is enforced unevenly. Hermann turns each factor into three artefacts: a card he reads when he writes a spec and briefs the coder, a check lane that scores the repository from 0 to 3 with the evidence it used, and a fix recipe that the proposal lane can pick up.
Here is what the twelve lanes actually look at. None of them asks a model anything.
| Factor | What the lane checks in a Spring Boot repository |
|---|---|
| I Codebase | A git checkout with exactly one remote on the git host, a main branch, no nested repositories |
| II Dependencies | Maven wrapper committed, no SNAPSHOT versions, dependency:analyze reports nothing used but undeclared |
| III Config | No literal credentials or hostnames in main resources, datasource URL bound to an environment variable |
| IV Backing services | Every backing-service setting environment-bound, a compose file present, no embedded database at runtime |
| V Build, release, run | Multi-stage Dockerfile, no build tool in the run stage, an image tagged by the commit it was built from |
| VI Processes | No HTTP session state, no local file writes, no static mutable collections |
| VII Port binding | server.port from the environment, jar packaging, the port declared in the Dockerfile |
| VIII Concurrency | No unlocked @Scheduled, no in-memory cache manager, JVM memory sized relative to the container |
| IX Disposability | Graceful shutdown with a timeout, exec-form entrypoint so SIGTERM reaches the JVM, measured startup |
| X Dev/prod parity | Compose runs the production engine, tests use Testcontainers, no embedded database in tests |
| XI Logs | No file appenders, structured console format, no System.out logging |
| XII Admin processes | A migration tool, versioned migration files, a one-off admin runner keyed by the environment |

Two honest caveats. The score is a heuristic, not a certification: it reads the POM, the resources, the Dockerfile, the compose file, the logging setup and the Java sources with targeted checks, and a determined developer can satisfy it without being twelve-factor. And a perfect score on a young repository is easy. The value is not the number. The value is that the number moves, carries evidence, and turns a regression into a proposal on the next iteration instead of a comment in a review three weeks later.
The coding agent is a token, not a call
The first version of the coder lane called Claude Code directly, with the binary name and the flags baked into a Python script. That is wrong for the same reason a hardcoded database URL is wrong: it makes a deployment decision into a code change, and it makes everyone who installs Hermann inherit my choice of vendor.
So the agent definition moved into the net, into a place that sits right next to the lane that uses it. One token per headless agent, and the token is the command template:
{
"agentId": "claude-code",
"title": "Claude Code (headless)",
"binary": "claude",
"command": ["claude", "-p", "--model", "${model}",
"--allowedTools", "${allowedTools}",
"--max-turns", "${maxTurns}",
"--no-session-persistence", "--output-format", "json"],
"promptVia": "stdin",
"resultFormat": "claude-json",
"defaultModel": "claude-opus-5",
"models": ["claude-opus-5", "claude-sonnet-5"],
"allowedTools": "Read,Grep,Glob,Edit,Write,MultiEdit,Bash(./mvnw:*),Bash(git status:*)",
"maxTurns": "80",
"timeoutMin": "45"
}
Whoever installs Hermann edits those tokens. Whoever operates him picks the agent and the model in the application, and the choice is a token write like any other. A Codex CLI definition ships next to the Claude one. Adding a third is a token, not a release. The prompt always travels on stdin, because a quoted argument can lose its quotes on the way through an executor and leave you with an agent running with no instructions at all.

Every run records which agent and which model produced it, so the history says who wrote what. When a future model is better or cheaper, the migration is a dropdown, and the comparison is honest because the spec, the verification and the review stay identical on both sides.
What it built, measured
Hermann built a throwaway service first, to prove the loop end to end. Then I pointed him at something real: a component of Agentic-Nets itself. The goal I gave him, in his own Goal tab:
A notification hub for Agentic-Nets. Operators register subscriptions: a source (a model, a place, an optional query filter, a poll interval) and a channel (a webhook URL with a shared secret). A poller reads new tokens through the gateway, renders a message, delivers it with retries and idempotent delivery records, and exposes a REST API to manage subscriptions and inspect deliveries. It exists so that a proposal waiting in an inbox, an approval request or an interview question reaches a person without anyone watching a screen.
That is a deliberately self-serving choice. The thing Hermann most obviously lacks is a way to tell me that he is waiting for me, and it is small enough to slice, and it exercises the factors that actually bite: a backing service, stateless workers, idempotent retries, one-off admin processes.
Two services, three merged pull requests, all numbers measured by the pipeline rather than reported by a model:
| sample-service, spec-001 | agentic-net-notify, spec-002 | |
|---|---|---|
| Scope | Create and read orders via REST | Subscription resource with a secure webhook channel |
| Coder | Claude Code, default model | Claude Code, claude-opus-5 |
| Diff | 13 files, 381 insertions | 28 files, 1342 insertions |
| Tests | 6 green | 18 green |
| Agent time | 556 s, 62 turns, $1.70 | 815 s, 48 turns, $4.73 |
| Verification | pass: startup 3.1 s, shutdown 0.3 s, JSON logs | pass: startup 3.1 s, shutdown 0.2 s, JSON logs |
| Review verdict | approve, no points | approve, no points |
| Scorecard after merge | 36 of 36, grade A | 36 of 36, grade A |

The shape of the time is worth a picture, because it is not what people assume. The model calls are cheap and fast. The coding agent is the whole cost, and everything else is rounding.
One detail I did not expect. The coder wrote its own field notes into the run record, and they were useful: that Spring Boot 4.1 moved to Jackson 3 and repackaged several test-slice annotations, that it chose three environment variable names the spec had left unspecified, and that it added one validation rule the spec did not mention. That last line is exactly what a reviewer needs to see, and it survived because the run is a token, not a scrollback buffer.
Why this shape holds
The state of the work is not in a chat log. The goal, the accepted decisions, every spec, every run, every verification, every verdict and every scorecard is a token in a place. Close the laptop, come back next week, and the next brief is built from the same facts. There is no context to re-establish because the context is the model, in the database sense of the word.
The human decision points are few, and they are the right ones. Choose the work, approve the contract, accept the result. Those are the three moments where judgement is expensive and irreversible. Everything between them is either measurement or bounded generation, and both are cheap to redo.
Measurement and judgement are separated by construction. A model that cannot count also cannot inflate. When the review says the tests pass, it is reading a number a script produced from a surefire report, not remembering what it saw scroll past.
Every part is replaceable while it runs. The coding agent is a token. The factor checks are ten Python files. The proposal prompt is an inscription you can edit in the Studio and see take effect on the next fire. Nothing here is a framework you have to fork.
| An agent in a terminal | A CI pipeline | Hermann | |
|---|---|---|---|
| Decides what to build next | You, in the prompt | Nothing, it only reacts | Proposes, you choose |
| Remembers past decisions | Until the session ends | No | Tokens in places, indefinitely |
| Acceptance criteria | In the prompt, then gone | Whatever the tests assert | An approved spec, kept in the repo and the net |
| Measures quality attributes | If you ask nicely | What you scripted | Twelve lanes with evidence, every commit |
| Runs while you sleep | Until it needs you | Yes, on a trigger | Yes, and it parks on a token when it needs you |
| Swap the model or vendor | New command line | Not applicable | A token edit, history keeps both |
What is not true yet
An article that only lists wins is marketing, so here is the other column.
- Hermann reviews his own work. The reviewer is a different lane with a different prompt and a measured diff, but it is the same family of model. A second opinion from a different vendor is one token away and I have not proven it is better. Treat the review as a checklist that catches sloppiness, not as an independent auditor.
- Three merged pull requests is not a track record. Two of them were on a throwaway service. The interesting failure modes (a spec that is wrong, a coder that gets stuck, a regression that the factor lanes do not catch) have not happened yet in front of me.
- The security net and the version-currency net are designed, not built. Dependency, code and secret scanning plus a threat pass per spec, and a weekly check of the Spring and Java lines, are the next two nets. The scanners are installed on the host and the design says a blocking finding refuses a merge. That is a plan, not a proof.
- The Codex definition is untested. It ships as a second option, but that binary is not on my executor host, so it has never run. The application shows the binary as missing, which is at least honest.
- It costs real money. Two features, $6.43 of agent time. That is cheap against a developer hour and expensive against nothing, and it scales with how much you let it retry.
- A person is still the only reviewer who can say “this is not what I meant”. The whole design assumes you show up three times per iteration. If you rubber-stamp, you get a fast machine for producing plausible code, which is exactly the failure mode this was built to avoid.
Try it
Agentic-Nets runs as a desktop install (one download, no Docker required for the runtime itself) or as a Docker Compose stack. Hermann needs a Docker host for his git server and the verification containers, plus a JDK, Maven and one headless coding agent on the machine that runs the executor.
# the runtime
git clone https://github.com/alexejsailer/agentic-nets
cd agentic-nets/deployment && cp .env.template .env
docker compose -f docker-compose.hub-only.yml up -d
# a package is one artifact: nets, scripts, seeds, application
node capabilities/tools/pack.mjs package --dir hermann
node capabilities/tools/pack.mjs publish --dir hermann
# install it into a fresh model, then open Studio and pick Applications
curl -X POST "$GATEWAY/api/hub/install" -H "Authorization: Bearer $TOKEN" \
-d '{"source":"local","name":"hermann","version":"0.1.0","targetModelId":"hermann"}'
Then: check the infrastructure, provision the git host, name your service, define the goal, and press the button that asks Hermann what to do first. He will tell you the goal is missing before he tells you anything else, which is the correct order.
Hermann was designed and built in a single working day on one laptop, using Claude Code inside Agentic-Nets, and the service he is now building is a component of Agentic-Nets itself. Every number in this article was measured by his own pipeline and read back out of the runtime, not estimated.
Related reading: Agentic-Nets: The First Multi-Layer Agent Runtime With Live Applications on Top for the runtime and the application layer, and Ten Reasons I Run Agentic-Nets Instead of n8n for how this differs from workflow automation.