Why every command runtime is capped at 200 lines
A 200-line cap on command runtimes, enforced by a hook. What it rejected, what that forced, and the one time the cap was the wrong tool.
Every command in this framework loads a runtime file that tells an agent how to execute it. Those files are capped at 200 lines, and the cap is enforced by a hook that refuses the write.
I hit it four times in one session. Every refusal improved the file.
The case this post is about
One file, five lines over a limit, and the refusal that fired on me:
BLOCKED — the file would be ~205 lines (limit 200)
1. Move NON-EXECUTABLE content to the reference doc:
banners · rationale/"why" prose · worked examples ≥15 lines · edge-case tables
2. Split cohesive EXECUTABLE blocks into a sub-module, moved VERBATIM, and
replace with: > Load _shared/<name>.md
3. The runtime keeps ONLY: step headings, every '> Load', table ROWS,
commands, halts
The file is a command's instruction set, loaded every time that command runs, so every line in it is a permanent cost paid against the work.
I was adding one executable line. This post follows those five lines over, because the limit forced four decisions I would otherwise have deferred indefinitely — and once it forced one that was pure overhead, which is also worth reporting.
The symptom
Instruction files grow. Not through carelessness — through care. Every hard-won detail is worth recording: this failed once, that ordering matters, here is why the obvious approach is wrong. Each addition is individually justified and the aggregate is a document nobody reads.
The cost is specific and easy to miss. A runtime is always loaded when its command runs. It is not reference material fetched on demand; it is resident context, competing with the actual task for attention. A 600-line runtime spends most of its budget on paragraphs that apply to one run in fifty, and the steps that apply every time are buried among them.
The failure mode is not that the file is long. It is that the executable parts stop being findable, and an agent skimming a wall of rationale misses a step.
Why the obvious fix failed
"Keep it concise" is not a constraint, it is an aspiration, and it loses to the next individually-justified paragraph. I know this because I wrote the rationale paragraphs myself and each one felt necessary at the time.
Nor does "move the rationale out" work as advice without a forcing function. The question does this belong in the runtime or the reference? has no natural answer — both are defensible for almost any paragraph, so the default is to leave it where you wrote it. The decision has no cost until something makes it expensive.
A hard number makes it expensive at exactly the right moment: when you are adding something.
The actual mechanism
The cap is enforced on write, and the refusal names the remedies:
BLOCKED — the file would be ~205 lines (limit 200)
1. Move NON-EXECUTABLE content to the reference doc:
banners · rationale/"why" prose · worked examples ≥15 lines · edge-case tables
2. Split cohesive EXECUTABLE blocks into a sub-module, moved VERBATIM, and
replace with: > Load _shared/<name>.md
3. The runtime keeps ONLY: step headings, every '> Load', table ROWS,
distinct commands, rule citations, decision branches / halts.
NEVER drop a step heading, a '> Load', a table row, a command, or a halt —
relocate, don't delete.

That last line is what makes the cap safe rather than destructive. Without it, the cheapest way past a line limit is deletion, and a limit that encourages deleting halts is worse than no limit. The constraint is on location, not existence.
Three-tier structure falls out of it:


The fix
What the cap actually forced, in four refusals:
Twice it removed prose that was already elsewhere. I was adding an explanation of why a brief must be built from the post rather than invented — which the brief sub-module already said, at length. The runtime needed the instruction, not the argument. Compressing it cost nothing, because nothing was lost.
Once it produced a sub-module that should always have existed. An asset contract had grown to a
table, placement rules, a conversion step and its rationale. That is not a step in a sequence; it
is a coherent document about one topic. It became _shared/medium-post-asset-set.md, and the
runtime kept two lines: what the minimum set is, and a > Load.
Once it was genuinely annoying. I needed one executable line — a second command in an existing block — and had to compress a paragraph elsewhere to afford it. The compression was fine. But the cap does not distinguish "one more command" from "forty more lines of prose", and in that moment the constraint was doing no work. I paid a real cost for no benefit.
I have not changed it, because the alternative — a cap that only counts prose — requires classifying every line, and a rule that needs a classifier is a rule with an argument in it. A crude limit that occasionally misfires beats a sophisticated one that can be litigated.
What makes the cap a forced decision rather than a nag is the refusal text: it names the three moves and closes the cheapest wrong one. The whole guard:
LIMIT=200
check_runtime() {
local file="$1" lines
lines=$(count_lines "$file")
[ "$lines" -le "$LIMIT" ] && return 0
# A line limit's obvious compliance route is DELETION, and the things most
# tempting to delete are the load-bearing ones: a step heading, a > Load, a
# halt. So the refusal closes that route by name before suggesting anything.
{
echo "BLOCKED — $file would be $lines lines (limit $LIMIT)"
echo
echo " 1. Move NON-EXECUTABLE content to the reference doc"
echo " 2. Split cohesive blocks into a sub-module, moved VERBATIM"
echo " 3. The runtime keeps ONLY: step headings, '> Load', table rows, commands, halts"
echo
echo "NEVER drop a step heading, a '> Load', a table row, a command, or a halt —"
echo "relocate, do not delete."
} >&2
return 2
}
And the companion check, which is the half that makes the first one safe:
check_nothing_lost() {
local before="$1" after_runtime="$2" after_submodules="$3" kind
for kind in '^#### STEP' '^> Load' '^\| ' '^HALT '; do
local was now
was=$(count_matching "$kind" "$before")
now=$(( $(count_matching "$kind" "$after_runtime") + $(count_matching "$kind" "$after_submodules") ))
if [ "$now" -lt "$was" ]; then
echo "REGRESSION: $kind went from $was to $now — relocation lost content" >&2
return 2
fi
done
return 0
}
The refusal that produced the asset sub-module, and the relocation check on the result:
$ check_runtime layers/idea/commands/idea-medium-post.md
BLOCKED — layers/idea/commands/idea-medium-post.md would be 205 lines (limit 200)
1. Move NON-EXECUTABLE content to the reference doc
2. Split cohesive blocks into a sub-module, moved VERBATIM
3. The runtime keeps ONLY: step headings, '> Load', table rows, commands, halts
NEVER drop a step heading, a '> Load', a table row, a command, or a halt —
relocate, do not delete.
$ check_nothing_lost before.md idea-medium-post.md _shared/medium-post-asset-set.md
(exit 0 — 14 step headings, 9 loads, 31 table rows, 4 halts, all still present)
The second command is the one I would add first if I were doing this again. A cap with no relocation check rewards exactly the wrong compliance: deleting a halt is a one-line diff that satisfies the limit, reads as tidying, and removes a refusal nobody will notice is gone until the thing it refused happens.
How we proved it
The measurable claim is that the cap forced relocation rather than deletion. Across four refusals:
runtime 199 / 200 lines
sub-modules 4, each one cohesive topic
steps removed 0
commands removed 0
halts removed 0
Nothing was lost. Two sub-modules that now exist — the brief contract and the asset set — are documents I would not have written without being forced to, and they are better than the paragraphs they replaced, because being a standalone document imposes its own coherence.
The counter-evidence is the fourth refusal, and I would rather record it than tidy it away: one line of executable content cost ten minutes of unrelated compression. The cap is a blunt instrument and it was bluntly applied. On balance it is still worth it, but "on balance" is the honest claim, not "always".
What the cap does not solve
Two things it cannot touch, and pretending otherwise would be the same mistake the cap exists to prevent.
It measures length, not quality. Two hundred lines of vague instruction passes; a hundred and ninety of precise, executable steps passes identically. Nothing in the mechanism can tell you a runtime is bad, only that it is not long. I have a file sitting comfortably under the cap that I would not defend on any other axis.
Sub-modules have no cap of their own, which is the obvious escape hatch and I have used it
without noticing. Move a hundred lines of rationale into a _shared file and the runtime goes
green while the total context loaded for that phase is unchanged. The limit is on one file, not on
the sum — and the thing that actually costs attention is the sum.
The partial defence is that a sub-module is loaded conditionally, when its phase runs, so the
always-loaded ceiling is genuinely lower. But "I moved it behind a > Load" is not the same as "I
made it shorter", and it is worth being honest that the first is what usually happened.
What keeps this from being a fiction is the rule about relocation, inverted: if a sub-module exists only to get under a cap, and is loaded on every run anyway, it has not helped. The test is whether a real run can finish without loading it. If not, the content never actually left.
What this generalizes to
Any always-loaded instruction file is a budget, and budgets that are not enforced are not budgets. A crude hard limit converts an unanswerable question — is this paragraph worth its space? — into a forced, local decision at the moment you are best placed to make it. Pair it with a rule that forbids deletion, so the only way past the limit is to put things where they belong.
One post a week, no digest
New writing when it lands. No roundups, no course pitch, no "10 tips".