Docs · Operate · Resume an existing system
Resume an existing system.
Most real work does not start on an empty repository: it is brownfield. You inherit a half-built service, rejoin a mission you paused, or face a system that was never built on any principle. runward's answer is a single read-only command, runward characterize, that turns an undocumented codebase into the factual starting point of a governed mission — and a workflow that tells you what to do with it.
What runward characterize does (and does not do)
runward characterize produces a factual inventory of an existing codebase and writes it to runward/characterization.md. It is deliberately, structurally limited:
- Read-only. It opens files and parses artifacts at rest. It never installs, builds, runs, or instruments the target, and makes no network call to it (ADR-0014).
- Writes only into
runward/. It never writes to your source tree and never touches.git/. Reading git history is done through read-only git commands likegit rev-list,git log, andgit shortlog, never a write. - Zero-LLM, deterministic. The whole command is pure parsing; two runs on the same commit produce the same output. No model is called and none is bundled. Note this scopes to the command — the LLM that refines candidates into argued decisions lives in your harness, downstream, never in runward.
- Facts, not decisions. The output header is stamped
confidence: highand states plainly that nothing in it explains why the system exists. It never emits "here is your architecture" or "here is your compliance posture" — that temptation is exactly what the contract forbids by construction.
Exit codes
0when the inventory is produced.2when there is no readable target directory.
What it reads
The scan walks the tree — bounded to depth 6, skipping heavy directories like .git, node_modules, dist, build, .venv, target — and collects:
- Dependency ecosystems. Node/JS/TS is parsed in full from
package.json: runtime dep count, dev dep count, and the actual dependency names. Python (pyproject.toml,requirements.txt,setup.py,Pipfile), Go (go.mod, counting require-block lines), and Rust/Java/Ruby/PHP are detected by manifest presence plus lockfile; for these, dependency counts are partial (onlyrequirements.txtandgo.modare line-counted; the rest report presence only). A missing lockfile is reported asnone(unpinned) in the rendered table. A malformedpackage.jsonis still recorded, with deps left unknown rather than crashing. - Entrypoints. Declared
main/bininpackage.json, plus convention files likeindex.ts,src/index.ts,src/main.ts,main.py,app.py,src/main.rs,main.go. - CI / pipelines. Every
.yml/.yamlunder.github/workflows/, plus.gitlab-ci.yml,azure-pipelines.yml,.circleci/config.yml,Jenkinsfile,.drone.yml. - Containers / deployment.
Dockerfile,docker-compose*,compose.yaml,vercel.json,netlify.toml,fly.toml,Procfile,Chart.yaml. - Tests. Presence of
test/,tests/,__tests__/,spec/directories, and a count of files matching test naming conventions across JS/TS, Go (_test.go), Python (test_*.py), and Java (*Test.java). No coverage figure is read or rendered — only directory presence and a file count. - Git shape. Counts only: total commits, first-commit and last-commit dates (span), and distinct author count. The rendered section states outright that the log tells you what changed and when, rarely why — and the why is a decision to reconstruct, not a fact to read. If the target is not a git repo (or git is absent), this is
nulland reported as such.
The rendered file also reports an approximate scanned file count.
Optional: candidate ADR mining with --mine
What it proposes
Add --mine to also propose candidate retroactive decisions. This is still deterministic git archaeology with no model call. It proposes:
- one candidate per detected stack;
- one for the deployment target (if any);
- one for the CI pipeline (if any);
- up to eight for notable dependency families (web framework / UI, data store / ORM, queue / messaging, AI / model provider, external-service SDK).
For each stack candidate it looks up the manifest's first-seen date via git log --diff-filter=A --reverse.
How candidates are written
Each candidate is written as runward/adr/DRAFT-<slug>.md with Status: hypothesis, the evidence pointers, why: UNKNOWN — the operator must supply the rationale, and a mandatory (empty) re-evaluation trigger.
- DRAFTs are written with
force:falseso a DRAFT you have already started editing is never clobbered. - Operator-ratified
ADR-NNNN-*.mdfiles are never targeted or overwritten. characterization.mditself is always refreshed (idempotent,force:true).
None of it can pass the gate
Nothing --mine emits can pass the gate: a DRAFT carries Status: hypothesis, and runward check --strict stays red until you rewrite the why, add a trigger, set Status: accepted, and rename the file to ADR-NNNN-<slug>.md. Ratifying a candidate is your call, not your agent's.
The recommended brownfield workflow
The command prints its own next steps, which route into runward/workflows/brownfield.md. The workflow's rule is: characterize before touching, never rewrite in one block.
Four entry modes
It identifies four entry modes and tells you to calibrate on the most demanding when they overlap:
- M1 — Resume your own mission. Artifacts exist. Reread them, rebuild state from the persisted artifacts (never memory), find the phase reached, pick up there.
- M2 — Join a project in flight. Inventory the system, then produce the missing upstream deliverables: a light retro-framing, an architecture note reconstituted by observation, and retroactive ADRs. Re-enter the chain, typically at
architect. - M3 — Audit then rebuild. A principled audit and a section-by-section gap analysis first, then a staged rebuild — never one block.
- M4 — Derive a new system from an old one. Treat the old system as requirements and constraints, not a base to copy; re-enter as greenfield at
frame.
Rebuilds (M3/M4)
For rebuilds (M3/M4), the workflow prescribes three moves in order, never skipped:
- Characterize behavior with characterization tests before any change — and note that these runtime tests belong to you in your own harness, not to a runward command.
- Install an anticorruption layer at the boundary.
- Replace progressively, strangler-style, each step guarded and reversible.
Every structuring choice you keep or take is locked as an ADR, then you re-enter the standard method chain with the same gates as greenfield.
Agent-operable, wire-boundary intact
An agent can drive this end-to-end: run runward characterize --mine, read runward/characterization.md and the DRAFT-*.md files, and help you reconstruct each why.
What it cannot do is launder a hypothesis into an accepted decision on its own — the gate stays red until you ratify.
And characterize never wires anything into your repo: it writes only under runward/, never .git/, consistent with runward's rule that adapters are inert samples the operator wires (ADR-0012). The read-only boundary here is a deliberate authorization choice, not a missing feature.
Quick reference
runward characterize # read-only inventory → runward/characterization.md
runward characterize --path ./svc # target a subdirectory
runward characterize --mine # also propose DRAFT retroactive ADRs (deterministic)
RUNWARD_DRY_RUN=1 runward characterize --mine # print intended writes, write nothing
Dry-run works two ways: the global --dry-run flag (runward --dry-run characterize --mine) or the RUNWARD_DRY_RUN=1 environment variable; both print the intended writes without touching disk. The command's own options are --path and --mine. Exit 0 = inventory produced; exit 2 = no readable target directory. --mine never changes the exit code: it is advisory.
Why this choice
The command is deliberately read-only and zero-LLM by construction, not convention. ADR-0014 records the alternatives rejected:
- Letting characterize RUN/instrument the system to observe real behavior — rejected because it violates never-a-runtime and would execute untrusted code; runtime characterization tests belong to the operator's own harness.
- Emitting ADRs directly or auto-accepting mined decisions — rejected because it launders a hypothesis into a validated fact and lets a machine guess satisfy the gate, breaking operator-owns-the-gate.
- Putting mining in the default path — rejected so the deterministic inventory stands alone, offline and reproducible.
- One combined artifact — rejected to keep confidence:high facts (characterization.md) sharply separate from hypothesis guesses (DRAFT-*.md).
The point is to make it structurally impossible for an agent reading a real codebase to emit conclusions that reach the gate.
What comes next
ADR-0014's re-evaluation trigger watches field use of characterize --mine. Two signals matter:
- operators promoting DRAFTs unread (trusting proposals without ratification);
- the deterministic inventory being asked to make judgments beyond parsing.
The response is to add friction to DRAFT promotion (an explicit per-ADR confirm) or narrow the inventory — never to let characterize drift toward asserting conclusions.