feat(flowrite): a standalone redundancy editor, ported from writer-assistant - #62
Merged
Merged
Conversation
Nothing in flowrite notices that a page defines Ledger in the Overview, again in Use Cases, and a third time above the API table. The reviewer grades structure, coverage and the 28 style rules; the fact-checker asks whether a claim is true, not whether it is repeated; rule 3 covers the shallowest slice of one of the three kinds of redundancy. Ported from writer-assistant, which is about to be deleted (see WRITER-ASSISTANT-MIGRATION.md §4). Its ~350-line phase — a fresh scanner session per round, [REDUNDANCY] lines parsed by regex, a reused fixer session, three rounds, an unresolvable set — is one agent here. That machinery existed because Flue 1.x sessions returned text and a fresh scan needed a fresh session; one agent can read, decide and edit. A standalone entry point, not a phase of the write flow. The page it edits already exists, already compiled, already passed review — charging every write run a delegation to re-read it for repetition spends tokens on every run for a problem that appears on some. It also closes half of audit gap §8: writer-assistant could be pointed at a page nobody was writing, and flowrite so far could not. The guide lives in a skill directory and is imported as a string rather than mounted, which is the pattern kind-docs.ts already uses for six reference files. Mounting was measured at "three tool round-trips to activate a skill and read its resource" — worth it for a writer carrying seven skills it uses some of, pure loss for an agent whose only job is this one, and it would add a way to fail: the model declining to activate, then editing the page without reading the bounds. The directory stays a skill so the drafter can mount it the day pre-empting redundancy beats cleaning it up. One bound is tightened against the original: never edit a code block. writer-assistant's strategy table said to keep the best of several repeated examples and remove the rest. mdoc blocks share one scope down the page, so that cut can break every block below it — and there is no mdoc verify after a standalone run to catch it. Repeated examples stay. Sonnet rather than the Haiku writer-assistant used. The work looks cheap and is not: every cut is a judgement about whether words carry anything, and this is the only role here that edits a page no gate re-checks. app.ts leaves the second agent unmounted and says why. Mounting two agents needs a Hono instance, hono is not a dependency here, and buying one to expose an agent nobody calls over HTTP is a dependency bought for a comment. `flue run src/redundancy.ts` reaches it directly; the 'use agent' scan registers it either way, so dispatch and schedules can too.
The question a run has to answer is not "did it cut something" but "did it cut the right things and leave the rest alone", and neither half is decidable on a page whose redundancy nobody planted. seeded-page.md carries 7 known redundancies and 5 decoys, and the decoys are the half that matters: an editor that cuts meaning from a correct page is worse than no editor, because the page it damages already passed review and nothing downstream re-checks it. The decoys are chosen to be the mistakes the guide's bounds exist to prevent: three code blocks that look alike and are not, a "because" that carries causation, a "first … then" sequence, the word "tally" recurring across distant sections, and one sentence — that `seeded` takes a Long, not an Int — which is the only place that fact appears on a page whose surrounding section reads repetitive. verify.sh plants the page in the tinyproject fixture, runs only this agent, prints the diff and the receipt, and removes the page from a trap so an interrupted run cannot leave the fixture holding a page the next run would then be editing. Its mechanical checks compare code-block text and heading text, never line numbers: removing a prose line shifts every number below it, so a numbered comparison would report every successful run as a failure. That was caught by exercising the checks against a simulated good run and a simulated bad one before committing them — the good run now passes and the code-block cut reports KILL. The label assertion rides in agent.test.ts's existing archive-label test rather than a new file. reduce-redundancy is not a KINDS row, but it is archived by the same script and fails the same silent way: a typo breaks `<label> token consumption:` parsing and nothing complains.
The spec merged yesterday put this at step 5 of all three instruction files, as a role in ROLES with 'redundancy' in the SkipPhase union. It shipped as a standalone agent instead, so the spec is rewritten rather than left to contradict the code — with the superseded placement kept as its own section, because the reasoning generalises: a maintenance pass over a page that already passed review is not a production step, and building it as a phase would have closed audit gap §4 while leaving §8 exactly as wide. BACKLOG finding 9 records the measurement this owes and why it is worse than finding 7's. A fact-checker that invents a drift wastes a round; an editor that cuts the wrong sentence ships a page with a hole in it. The bounds are the whole defence and they are unmeasured prose, so the finding names kill criteria rather than tuning knobs: a touched code block, a moved heading, or a cut decoy means delete the agent. Two smaller unknowns are named there too — the three-occurrence threshold for a repeated phrase is a guess, and whether the receipt's "left" lines actually get written. The second is the interesting one: an instruction asking for a report of inaction is exactly the kind that gets quietly skipped, which agent.ts already measured once with ask_for_clarification.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gap §4 of
WRITER-ASSISTANT-MIGRATION.md, ported before that repo is deleted.grep -ri redundan src/returned nothing before this: the reviewer grades structure, coverage and the 28 style rules; the fact-checker asks whether a claim is true, not whether it is repeated; rule 3 covers the shallowest slice of one of the three kinds.It is not a phase of the write flow
The spec merged in #60 put it at step 5 of all three instruction files, as a role in
ROLESwith'redundancy'in theSkipPhaseunion. That was rejected before implementation and the spec is rewritten here rather than left to contradict the code — with the superseded placement kept as its own section, because the reasoning generalises.The page it edits already exists, already compiled, already passed review. Charging every write run a delegation to re-read it for repetition spends tokens on every run for a problem that appears on some — and the drafter that just wrote the page is the party better placed not to create the repetition in the first place. Building it as a phase would also have closed §4 while leaving audit gap §8 ("flowrite has no standalone entry points") exactly as wide.
So a write run is byte-for-byte what it was:
instructions/*.md,agent.ts,composition.ts,run-context.ts,self-report.tsandrun-telemetry.tsare untouched, andagent.test.ts's harness-tool invariant still expects exactly['review_page', 'fact_check_page']. The trade is explicit: the pass is available, not automatic.What writer-assistant's version had, and what came over
Its ~350-line phase — a fresh scanner session per round,
[REDUNDANCY]lines regex-parsed byparseFindings(), a reused fixer session, three rounds, anunresolvableset — is one agent here. That machinery existed because Flue 1.x sessions returned text and a fresh scan needed a fresh session. No harness tool either: nothing gates on redundancy, so the result only ever goes into the model's head and comes back as prose, and a schema would cost two relay turns for nothing.The one bound tightened against the original: never edit a code block. writer-assistant's strategy table said to keep the best of several repeated examples and remove the rest. mdoc blocks share one scope down the page, so that cut can break every block below it — and there is no mdoc verify after a standalone run to catch it. Repeated examples stay, and the guide says why.
Where the content lives
Answering a review question from #60's discussion: the guide is a skill resource, imported as a string, not a mounted skill.
This is the pattern
runtime/kind-docs.tsalready uses for six reference files, and it carries the measurement: mounting costs "three tool round-trips to activate a skill and read its resource, each re-sending its whole accumulated context … 2 round-trips wasted inwrite-data-type-ref-turn20and 5 inwrite-module-ref-turn5, which is why600f48aunmounted it". Worth it for a writer carrying seven skills it uses some of; pure loss for an agent whose only job is this one — and it would add a failure mode, the model declining to activate and then editing the page without ever reading the bounds.writing-styleis mounted, for the mirror-image reason: its 28 rules are consulted only when a cut happens to touch one.The directory stays a skill so that the day the drafter should pre-empt redundancy rather than have it cleaned up afterwards,
useSkill(reduceRedundancy)is a one-line change with no content moved.Tier, and why not Haiku
writer-assistant ran this on Haiku. The work looks cheap — find repetition, delete it — and is not: every cut is a judgement about whether the words carry anything, and this is the only role in the repo that edits a page no gate re-checks. Sonnet /
low, overridable withREDUNDANCY_EDITOR_MODEL/_EFFORT.app.tsleaves it unmounted, deliberatelyMounting two agents needs a Hono instance with a
route(...)each, andhonois not a dependency here — it reaches this repo only as@flue/runtime's peer. Adding it to expose an agent nobody calls over HTTP is a dependency bought for a comment; hand-rolling aFetchablethat dispatches by path prefix replaces the dependency with untested routing code on the one path flowrite's own runs never take.flue run src/redundancy.tsreaches it directly, and the'use agent'scan registers it either way — confirmed in the build output, sodispatch(...)and schedules can drive it.app.tsrecords all of this where the next person will look.Verification
Offline, done:
tsc --noEmitclean, 102 tests pass,vite buildsucceeds and the bundle contains the new agent.test-fixtures/redundancy/verify.shis the behavioral test and needs a live model, so it is blocked until the key renews (2026-09-01 — the same block as #59). It plants a page with 7 seeded redundancies and 5 decoys and diffs the result. The decoys are the half that matters, and they are chosen to be exactly the mistakes the bounds exist to prevent: three code blocks that look alike and are not, abecausethat carries causation, afirst … thensequence, the word "tally" recurring across distant sections, and one sentence — thatseededtakes aLong, not anInt— which is the only place that fact appears, sitting in a section that reads repetitive.Its mechanical checks compare code-block text and heading text, never line numbers: removing a prose line shifts every number below it, so a numbered comparison would report every successful run as a failure. That was caught by running the checks against a simulated good run and a simulated bad one before committing them — the good run passes, the code-block cut reports
KILL.BACKLOG.mdfinding 9 records the measurement this owes, and states kill criteria rather than tuning knobs. A fact-checker that invents a drift wastes a round; an editor that cuts the wrong sentence ships a page with a hole in it. If the seeded run touches a code block, moves a heading, or cuts a decoy, the answer is to delete the agent, not tune it.Not in this PR
Running that test. Deleting
writer-assistant. The two remaining salvage candidates from the audit (§1, the 569-line mechanical style checker; §2, the implicit-trace rule) — both deterministic and neither blocked by the key.