Skip to content

Releases: fabrodz/milestoner

v0.10.0

Choose a tag to compare

@fabrodz fabrodz released this 29 Aug 22:11
2d8fc25

The transcript of a running session is readable while it runs - in the panel, and by the supervisor that has to decide whether to step in.

Changed

  • One transcript renderer, in src/transcript.ts, behind the panel's /api/transcript and milestoner transcript. The page no longer decodes anything; a test asserts it never grows a parser back. A line without a top-level type is not an event, so a plain-text transcript and NDJSON of another shape (codex exec --json nests its type under msg) are passed through exactly as written, which is what keeps every agent watchable.

  • The default agent.args run Claude Code with --output-format stream-json --verbose. A headless claude -p with the default text format writes nothing until it exits, so the panel's "watch the live transcript" opened a file that was empty for the whole session. It now fills turn by turn while the session works: an event appears when a turn completes or a tool returns, so a long generation is a still frame rather than a stall.

  • Infrastructure classification weighs what the agent produced, not what the transcript file holds. A stream-json session opens with a four-kilobyte system/init tool inventory and closes with a result envelope; counted as evidence those alone cleared tinyTranscriptBytes and crashTranscriptBytes, which would have charged an attempt for every infrastructure death. readTranscriptEvidence discounts them, so both thresholds keep their existing calibration and a plain-text transcript from any other agent weighs exactly what it always did. See D-040.

  • The init inventory is no longer searched for infraFailurePatterns or usageLimitPatterns. It lists every installed skill and slash command, so a session could be classified from somebody else's prose. The result envelope still is, but only its words - result, subtype and api_error_status - and not the kilobyte of costs, token counts and hex uuids around them, any of which can carry the 429 pattern.

Added

  • A usage limit the agent reports outright (a rate_limit_event with status rejected, and its epoch resetsAt) is checked before the text patterns and outside the deathSeconds bound they sit behind, so a quota that ran out mid-session refunds the attempt and waits for the announced reset. Only rejected counts: allowed_warning means the limit is near, not reached, and a session runs straight through it. The last event decides.

  • milestoner transcript [--name <file>] [--lines <n>] [--raw] prints what the running session is doing, as prose rather than as its wire format: the agent's own text, > for a tool call and < for its result, cut to 240 characters. Its first line names the session and says whether it is still going, so a transcript quoted in an intervention cannot silently be a finished one; --raw omits it. Read-only, and outside the supervisor's intervention set on purpose - knowing what a session is doing has to be free to be done every cycle.

  • The panel follows an open transcript while the session runs, refreshing every 2.5s, and shows the same rendering the command prints. The scroll sticks to the bottom only while the reader is already there, and following stops when the pulse says no session is writing to that file - which also ends the pointless follow of a transcript opened from the attempt history.

  • The bundled supervisor skill reads the transcript every cycle and quotes what it saw when it intervenes. It also no longer claims a transcript proves nothing: liveness and diagnosis are now two separate questions, with the transcript answering only the second. Re-run milestoner skill install --force to pick this up - the installer will not overwrite a skill you already have.

  • milestoner lint warns (agent-not-streaming) when a claude agent is configured without stream-json, which is what an existing .milestoner/config.json written before this release will be.

Upgrading

  • Re-run milestoner skill install --force to replace an already-installed supervisor skill; the installer will not overwrite one you already have.
  • Existing .milestoner/config.json files have no streaming flags. milestoner lint warns (agent-not-streaming), but it is a warning: the run still starts, the panel just stays empty until the args are updated.

Compatibility

Only Claude Code's stream-json is decoded. A line without a top-level type is not an event, so plain-text transcripts and NDJSON of another shape pass through byte for byte and no agent is worse off than before. The per-agent table is in the guide, under Running a different agent.

v0.9.2

Choose a tag to compare

@fabrodz fabrodz released this 25 Aug 22:37

Added

  • Every attempt now records the model that ran it, not just the agent. Resolved at launch alongside the agent (runner.ts), it lands on the AttemptRecord, on pulse.json while a session is live, and in run-log.md's launch line - surfaced in the panel's milestone cards and attempt tables, the standalone report, and milestoner status. The field is a plain config string, the same one already threaded through agent.modelArgs, so it needed no per-agent logic: it records correctly whatever the agent, Claude, Codex, or a fallback under any name.

Changed

The panel got a pass on its rough edges and its visual identity:

  • The "watch the live transcript" button did nothing when clicked, and the per-attempt "transcript" link and both unblock buttons had the same bug: each built its onclick attribute by splicing JSON.stringify(...) straight into a double-quoted HTML attribute, and JSON.stringify's own quotes closed the attribute early. The value is now HTML-escaped before it goes in.
  • The report link fell back to unstyled browser-default blue with no CSS of its own. It is now a real button, styled like every other action on the page, and opens in a new tab so the live panel is not lost.
  • The transcript viewer scrolled sideways on long lines. It now wraps and scrolls vertically only.
  • The config editor had no guidance on what it accepts. A collapsed reference now documents every field plus copy-paste agent blocks for Claude, Codex, and Ollama-via-Codex.
  • IDs, timestamps, durations, and agent/model names now render in a tabular monospace face throughout (stat tiles, milestone cards, attempt tables, lint findings, activity log, interventions), while narrative text (the verdict, a diagnosis, evidence) stays sans-serif - a console feel that fits a coding-agent supervision tool better than a generic SaaS dashboard. Added :focus-visible and :active feedback, previously absent on every control.
  • Zero em-dashes left in visible panel text.

v0.9.1

Choose a tag to compare

@fabrodz fabrodz released this 25 Aug 21:36

Fixed

  • The panel's transcript buttons - "watch the live transcript" and the per-attempt "transcript" link - and both unblock buttons did nothing when clicked. Each built its onclick attribute by splicing JSON.stringify(...) straight into a double-quoted HTML attribute; JSON.stringify's own quotes closed the attribute early, leaving onclick holding invalid JavaScript. The value is now HTML-escaped before it goes in, matching every other attribute the panel builds this way.

v0.9.0

Choose a tag to compare

@fabrodz fabrodz released this 22 Aug 20:50

A run can be created, written, configured, started, corrected and read from the browser. The panel
was already the run's control surface once a run existed; what it grew here is everything before
that and everything the CLI could do that it could not. The walkthrough is in
the guide; what stays outside the browser
is bringing the panel up.

Added

  • A milestone can be added to an existing run, mid-run included. milestoner add [--title <text>]
    appends one pending milestone - the next id after the highest in state.json, a prompt skeleton
    in .milestoner/prompts/ - under the state lock, and prints the id and the prompt path. Until
    now the count chosen at init was final short of hand-editing state.json, a file the engine
    owns.
  • The append is safe while a runner is alive: the runner picks its next milestone from a fresh
    state load on every loop pass, so an added milestone is reached when its turn comes, with no
    restart. Appending to a completed run clears runComplete - a run that gains a milestone is a
    run again. A prompt file already sitting at the skeleton's name is kept, so the prompt can be
    written before the slot is added.
  • The panel grew the same control: an Add a milestone card under the milestone list, posting
    to POST /api/milestone/add ({ title } optional) behind --write, the key, the Host
    allowlist and the Origin check like every mutation. The reply names the new id, and the card
    appears on the next refresh; both front ends call the one engine primitive.
  • A run can be created from the panel. The hub grows a New run card - directory, optional run
    name, milestone count - posting to POST /api/init, which calls the same init() the CLI does,
    so the scaffold and its refusals are the command's. The new project is recorded in
    ~/.milestoner/projects.json, so it joins the hub listing on the next refresh with no CLI command
    run anywhere.
  • POST /api/init validates its body before init() sees it: the path must be absolute and an
    existing directory (a relative one would resolve against the panel daemon's working directory, and
    a missing one is refused rather than created), milestones takes the CLI's 1-99 bounds, and
    force must be an explicit true. It is behind --write, the key, the Host allowlist and the
    Origin check like every other mutation, and it is a machine-panel route: a panel serving one
    project answers 404. Only an existing-config refusal reveals the force checkbox; a protocol naming
    another run (D-030) is refused even with force and says which run it names, because force cannot
    answer it. The reasoning for accepting a filesystem path over HTTP is D-038.
  • .milestoner/config.json can be read and edited from the panel. The per-run view carries the whole
    document in a text box, fed by GET /api/config and saved by POST /api/config, so every key -
    the infra thresholds, fallbackAgents, liveness, environment, the agent command - is
    reachable without leaving the browser. A runner that is already going read its config at startup,
    so an edit applies to the next one; the card says so rather than blocking the edit.
  • A save is validated by the loader itself: the submitted text is parsed and put through the same
    checks loadConfig runs on every runner start, and only a document that passes is written, through
    the same atomic write every other engine write uses. A refusal carries the loader's own sentence
    (missing required field "agent", or the JSON parser's position) and leaves the file byte for byte
    as it was, so nothing that would stop the next runner from starting can be saved from the panel.
    projectRoot is dropped rather than written, as init has always left it out.
  • A model per milestone: models in .milestoner/config.json maps a milestone id to the model its
    session runs on ({"M03": "opus"}), so a plan can spend a cheap model on the mechanical
    milestones and a stronger one on the hard ones. It is resolved at each session launch, not once
    at startup, so an edit mid-run applies from the next session. --model overrides the whole map;
    a fallback agent keeps its own model, because model names are not interchangeable across
    agents. milestoner lint warns (orphan-model) about a models key naming no milestone in
    state.json, which is otherwise a model silently never used.
  • A model field on every milestone card, holding that milestone's entry in the models map and empty
    when it has none. Saving reads the config, changes that one key and sends the whole document back
    through the same validated endpoint; clearing the field removes the entry and the milestone goes
    back to the agent's own model.
  • The panel starts a run with the same options the CLI takes. POST /api/run/start accepts
    milestone, once, maxAttempts and model beside noLint and translates each to its flag on
    the spawned runner; the start control grows a collapsed options row with a milestone picker built
    from the run's own ids, a "one session, then stop" box, an attempts field and a model field, all
    optional, posting only what was filled in. "Unstick the environment" gains a seconds input that
    overrides environment.attendSeconds for that one run of the adapter.
  • Start options are validated in the panel's process before anything is spawned: an unknown or
    empty milestone, a maxAttempts that is not a positive integer, an empty or non-string model
    and a non-boolean once are refused with a message naming the field and no runner started. The
    runner is spawned detached with its output discarded, so a flag it would reject would otherwise
    fail where nobody can see it.
  • The machine panel lists every project on the machine, not only the ones whose runner is alive or
    started while the panel was up. Every command that works inside a project records its directory in
    ~/.milestoner/projects.json (init included), and the hub summarises the ones the registry has
    never heard of from their own state.json, reported unknown rather than gone because nothing
    died there. They resolve for every control, so a run can be started, steered or unblocked from the
    browser after a reboot. Writing the file is best-effort, a corrupt one is treated as empty, and an
    entry whose directory is gone is skipped and left in place.
  • The milestone prompts and the protocol can be written from the panel. Each milestone card carries
    an editor for its prompt file, collapsed behind an "edit the prompt" link, fed by
    GET /api/prompt?name=<file> and saved by POST /api/prompt; a Protocol card holds
    .milestoner/protocol.md through GET and POST /api/protocol. Nothing structural is checked
    before writing - both files are hand-written prose by design, so the lint card is the feedback
    rather than a write gate, and it refreshes on a save so filling in a skeleton visibly clears its
    template-residue findings. A prompt is reachable only by a name some milestone's prompt field
    carries: path separators, a missing .md or a name no milestone owns are refused, read and write
    alike. Writes are atomic and behind --write, the key and the Origin check like every other
    mutation; an edit applies to the next session launched, as steering does, and the editors say so.
  • Direct tests for the api.ts handlers, which the panel's whole write surface goes through and
    which until now were only exercised indirectly over HTTP.
  • The report served by the panel carries a link back to the view it came from, and one line saying
    what it is: the run's archival snapshot, the same self-contained file milestoner report writes,
    where the panel is the live view. The link carries exactly what the report's own URL carried - the
    run on a machine panel, the key only when the caller used one - so a page reached through the
    cookie --open sets never gains a key in its history. The file on disk passes no such link and so
    carries none: one renderer, and only the served consumer knows a panel exists.
  • Every card on the panel says what it is for, in one visible line under its heading: the verdict
    banner spells out what alive, slow, hung and gone mean, the lint card that error-level findings on
    pending milestones refuse a start, steering that a correction is read by the next session launched
    and not by the one running, the interventions card that its lines are outside actions on the run,
    the engine log which file it is reading, and the report link what a self-contained report is good
    for. Every consequential control carries a tooltip naming what it does and what it costs - kill
    spends an attempt and brings the runner straight back, "start anyway" skips the gate and leaves
    the findings unfixed, stop costs nothing. The subtitles are visible text rather than tooltips
    because a tooltip does not exist on a touch screen.

Fixed

  • An empty supervisor log no longer renders as two interventions. supervisor-log.md opens with a
    title and a backtick-quoted format line, written by init before anything has ever happened, and
    the panel's INTERVENTIONS card and status --json's recentInterventions both tailed the file
    raw and served them. Reading the log is now one shared function that drops its own header, used by
    the panel, status and both reports, and a run nobody has intervened in says so.

  • The milestone card a session is working on shows that session. The card body rendered only graded
    attempt history, so while attempt 1 ran the page contradicted itself: the verdict banner said
    "Working on M01, attempt 1" while the card below read "IN PROGRESS", "no attempts used" and "Not
    started yet." Now, when the pulse names the milestone and the runner is alive, the card carries a
    live block - when the session started, the agent when the pu...

Read more

v0.8.0

Choose a tag to compare

@fabrodz fabrodz released this 21 Aug 16:41

A run's form is now checked before its hours are spent. milestoner lint verifies every milestone
prompt against the conventions the evidence gate relies on - criteria that name their evidence, an
exit section with the run's tag, no scaffold residue - milestoner run refuses to start a run that
fails it, and the web panel reads, gates and overrides with exact parity to the terminal. The line
between mechanical form and human judgement is D-035: the linter checks form, the planner keeps
judgement. The feature was built by milestoner supervising itself, the third such run (v08-lint,
four milestones, all closed on the first attempt).

Added

  • milestoner lint: checks the run's form - every milestone prompt, the protocol header and the
    config - before a session spends real time on it. Errors (missing prompt file, scaffold residue,
    missing objective or criteria, a criterion without an evidence note, an exit section without the
    run's tag) exit 1; warnings (orphaned prompt, protocol naming another run, empty liveness)
    alone exit 0. --json prints { run, errors, warnings, findings } for scripts. The rules are
    the pure lintRun core in src/lint.ts; the line between form and judgement is D-035.
  • milestoner run lints at startup: error-level findings on milestones that are still pending
    refuse the start with the findings printed the way milestoner lint prints them, exit 1,
    before any session, state change or panel. --no-lint skips the gate; findings on done or
    blocked milestones never stop a resume, and warnings never block. Every start, gated, clean or
    bypassed, writes one lint summary line to run-log.md.
  • The web panel keeps lint parity with the terminal: GET /api/lint returns exactly what
    milestoner lint --json prints, behind the same auth as every API route; the panel shows a lint
    card with the counts and the per-milestone findings before anything is started. Starting a run
    from the panel lints first, in the panel's process (the runner is spawned detached with its
    output discarded, so its own gate would refuse invisibly): error-level findings on pending
    milestones refuse with the counts and the first findings in the message, and a deliberate
    "start anyway" control passes --no-lint through to the spawned runner so the run log records
    the bypass.

Removed

  • publish.yml, one release after it arrived. Its first exercise ended at npm auth (the trusted
    publisher was never configured) and 0.7.0 went out by hand; a publish is one command with
    prepublishOnly already gating it, so the automation was overhead, not safety.

Fixed

  • bin drops its ./ prefix. npm 11 warns that the ./dist/cli.js form is invalid and rewrites
    the manifest while publishing; the plain relative path publishes clean, and npm 10 accepts both.

v0.7.0

Choose a tag to compare

@fabrodz fabrodz released this 21 Aug 11:12

The run gets a planner and the machine gets one panel. The planner skill authors the run's plan
with you in conversation without ever inventing an acceptance criterion; the machine panel comes
up with the first milestoner run and spans every run on the machine. The Claude Code plugin is
retired: it could not do anything without the npm-installed binary its commands and skills shell
out to, so milestoner skill install is now the one way the skills reach a machine.

Added

  • The planner skill, milestoner-planner: plans a run together with the user - it interviews,
    proposes the milestone breakdown for approval, and only then writes the prompts, the protocol
    TODOs and the liveness config. It never writes before approval and never invents an acceptance
    criterion, so the boundary against generated specifications (D-031) stands; the argument is
    D-032. Shipped like the supervisor skill: one source in src/templates/, written by
    milestoner skill install.
  • milestoner init now points at the planner skill for the authoring steps it cannot do itself.
  • milestoner skill install -g as the short form of --global.
  • The machine panel (D-033): milestoner serve --all serves every run the registry knows about,
    from any directory - a hub across runs plus the familiar per-run view with a switcher. The first
    milestoner run on the machine brings it up as a detached daemon that stays while any run is
    alive (plus a ten-minute linger) and then exits and cleans up after itself; every run prints its
    URL, discovery is ~/.milestoner/panel.json, and milestoner runs names the URL when a panel
    is live. --no-panel opts a run out.
  • milestoner run --open opens the machine panel in the browser without writing the key into
    browser history: the CLI mints a single-use token and the panel exchanges it at /auth for an
    HttpOnly cookie, so the URL history keeps is already dead. By default the browser opens only on
    the run that started the daemon; --no-open stops even that. D-027's refusal of --open for
    the attached run --serve panel stands.

Changed

  • milestoner skill install takes an optional skill name (supervisor, planner, or the full
    names). With no name it installs every bundled skill, where it previously installed only the
    supervisor; --print now requires a name.
  • milestoner run now brings the machine panel up by default (read-write: kill, steer and unblock
    at 3am are why it exists; the guards are the same loopback bind, Host allowlist, key and Origin
    check as always). run --serve keeps its per-run attached panel unchanged and skips the machine
    panel.

Removed

  • The Claude Code plugin and its in-repo marketplace (D-034). The plugin could not do anything
    without the npm-installed binary its commands and skills shell out to, so it was a second copy
    of the npm channel, not a second channel. milestoner skill install is now the one way to get
    the skills, and with them gone go commands/, the generated skills/ mirror, the manifest
    sync/check scripts, and the manifests CI job.

v0.6.1

Choose a tag to compare

@fabrodz fabrodz released this 21 Aug 02:23

Documentation only; the engine is byte-identical to 0.6.0. The package's front page on npm is the
README from its own tarball, so a README this size and this wrong about its own licence was worth a
patch release rather than waiting for the next feature.

Fixed

  • The licence badge claimed Apache 2.0. The project is MIT, as LICENSE, package.json and the
    published package all say, and it always has been. The CI badge pointed at an unrelated
    repository's workflow.
  • README images used repository-relative paths. The published tarball carries four files and
    docs/assets/ is not among them, so those images rendered on GitHub and broke on the package
    page. They are absolute now.

Removed

  • BRIEF.md, the genesis document. Its architecture section was the only place recording that the
    engine is generic, the protocol is templated and milestone prompts are always hand-written, and
    three documents cited it; that claim is now D-031, where a decision belongs. Its "product
    decisions to make first" section had been resolved into D-001 to D-009 since 2026-08-18, and its
    suggested roadmap stopped at v0.4 and had been overtaken.
  • docs/PLAN-v05.md, a plan that has been executed. What it planned is in the [0.5.0] and
    [0.6.0] entries in the changelog and in D-025 to D-030.
  • docs/PLAN-flow-authoring.md. Its substance, including the milestoner lint experiment that
    would settle the question with data instead of taste, is folded into item 5 of docs/NEXT.md.
  • docs/runs/, the archived record of the v04-plugin run, and .milestoner/ is now gitignored
    rather than partly tracked. A run's state and evidence belong to the machine that ran it. Both are
    in this repository's git history up to v0.6.0 for anyone who wants to read what those sessions
    wrote.

Documentation

  • README.md is a quarter shorter. Five sections duplicated docs/GUIDE.md and one of them, the
    agent recipes, had drifted into being a strict subset of it. Each now carries what a reader needs
    before installing and links to the guide for the rest. The panel's security model stays in the
    README, condensed rather than moved, because a write-enabled panel is a remote code execution
    endpoint and that is not a detail to look up elsewhere.
  • The roadmap's validation paragraph said v0.5 was a four-milestone run. It was seven, three of
    which were bugs the run found in the engine while executing.

npm install -g milestoner

On npm: https://www.npmjs.com/package/milestoner/v/0.6.1

v0.6.0

Choose a tag to compare

@fabrodz fabrodz released this 21 Aug 00:32

The first version published to npm, and the first three milestones the engine set for itself. M01 to
M04 of the v05-debt run were debt planned in advance; M05, M06 and M07 were bugs that run exposed
by executing, in the engine that was executing it. Two of the three cost the run real attempts before
anyone knew they existed.

A minor rather than a patch, despite reading as a list of fixes: init now refuses where it
previously proceeded, the tag scheme the templates write has changed, and there is a new
infra.crashTranscriptBytes option.

Added

  • infra.crashTranscriptBytes (default 100 B), the threshold below which a transcript is read as a
    crash regardless of how long the session ran. See the refund entry under Fixed.

Changed

  • The protocol and milestone templates tag green milestones <run>-<milestoneId> instead of
    <run>/<milestoneId>. A tag with a slash collides with the branch name a session naturally picks
    for the same milestone, and git push origin <name> then fails with src refspec matches more than one. Existing tags are untouched. Recorded as D-030.

Fixed

  • milestoner init over an existing .milestoner/ no longer silently keeps a protocol that names a
    different run. The protocol is hand-edited, so init still never rewrites or deletes it; what it
    does now is stop with exit 1 before scaffolding anything when .milestoner/protocol.md names
    another run in its header, and say what to bring in line. Previously every session of the new run
    was handed the finished run's rules, tag instruction included, and nothing said so - it happened
    to this repository's own v0.5 run. Recorded as D-030.
  • A session that crashed mid-run no longer costs the milestone an attempt. The infra classifier
    read a tiny transcript as a crash only inside infra.deathSeconds (90 s), so an agent that
    worked for fifteen minutes and then died leaving fifteen bytes was graded incomplete and
    charged - it happened to the v0.5 run itself, on a milestone whose work was already done. A
    transcript below infra.crashTranscriptBytes (new, default 100 B) with no result.json is now
    refunded as a crash at any duration; a session that left a real transcript and no result is
    still charged, and the refund shares the existing ceiling of infra.maxRetries consecutive
    infrastructure failures. Recorded as D-029.
  • Two milestoner processes writing state at the same moment could silently lose one of the writes:
    an unblock issued while the runner was grading could vanish as if never typed, and a run could
    be missing from milestoner runs even though its runner was alive (seen once in CI, where one of
    six simultaneous runners was absent from the listing). The state lock that should have prevented
    this could be broken by a contender in the first instant after it was taken; the lock now names
    its holder from the moment it exists, a lock that cannot be read yet is waited out instead of
    discarded, and the lock is no longer stolen after a 5-second wait. The worst case for a contender
    facing a crashed or wedged holder is a bounded wait (3 or 30 seconds), never a lost update.
    Recorded as D-028.

npm install -g milestoner

On npm: https://www.npmjs.com/package/milestoner/v/0.6.0