bareloop 0.41.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +109 -1
- package/README.md +13 -21
- package/bareloop.context.md +97 -34
- package/package.json +4 -3
- package/src/authorflow.js +337 -29
- package/src/authoring.js +92 -7
- package/src/authorjob.js +84 -16
- package/src/authorreadout.js +89 -5
- package/src/authorrun.js +14 -5
- package/src/authorscout.js +108 -0
- package/src/declaredclose.js +25 -14
- package/src/facts.js +317 -0
- package/src/factwords.js +129 -0
- package/src/inputlook.js +301 -0
- package/src/interviewrun.js +0 -1
- package/src/job.js +40 -12
- package/src/jobblock.js +7 -2
- package/src/kinds.js +84 -3
- package/src/labelscan.js +170 -0
- package/src/pagefetch.js +125 -0
- package/src/panel/authorroutes.js +121 -34
- package/src/panel/authorsession.js +154 -45
- package/src/panel/index.html +845 -490
- package/src/panel/jobplan.js +34 -11
- package/src/panel/runroutes.js +163 -5
- package/src/panel/scheduler.js +156 -0
- package/src/panel/schedules.js +667 -0
- package/src/panel/schedulestart.js +243 -0
- package/src/panel/server.js +467 -48
- package/src/planrun.js +42 -9
- package/src/providerrows.js +3 -3
- package/src/reviewdoor.js +37 -0
- package/src/rubricstep.js +804 -0
- package/src/runlist.js +35 -0
- package/src/schedulewords.js +296 -0
- package/src/source.js +166 -1
- package/src/trigger.js +48 -0
- package/src/triggerclock.js +127 -0
- package/src/triggerfiles.js +215 -0
- package/src/triggerledger.js +79 -0
- package/src/u-readout.js +1 -1
- package/src/userrun.js +20 -9
- package/types/authorflow.d.ts +267 -13
- package/types/authoring.d.ts +29 -1
- package/types/authorjob.d.ts +7 -5
- package/types/authorreadout.d.ts +27 -2
- package/types/authorscout.d.ts +70 -0
- package/types/declaredclose.d.ts +5 -3
- package/types/facts.d.ts +71 -0
- package/types/factwords.d.ts +42 -0
- package/types/inputlook.d.ts +238 -0
- package/types/job.d.ts +8 -1
- package/types/jobblock.d.ts +8 -1
- package/types/labelscan.d.ts +38 -0
- package/types/pagefetch.d.ts +71 -0
- package/types/panel/authorroutes.d.ts +34 -1
- package/types/panel/authorsession.d.ts +55 -14
- package/types/panel/jobplan.d.ts +12 -4
- package/types/panel/runroutes.d.ts +32 -1
- package/types/panel/scheduler.d.ts +17 -0
- package/types/panel/schedules.d.ts +412 -0
- package/types/panel/schedulestart.d.ts +106 -0
- package/types/panel/server.d.ts +145 -14
- package/types/planrun.d.ts +2 -0
- package/types/providerrows.d.ts +2 -2
- package/types/reviewdoor.d.ts +27 -0
- package/types/rubricstep.d.ts +286 -0
- package/types/runlist.d.ts +20 -0
- package/types/schedulewords.d.ts +135 -0
- package/types/source.d.ts +94 -0
- package/types/trigger.d.ts +37 -0
- package/types/triggerclock.d.ts +52 -0
- package/types/triggerfiles.d.ts +93 -0
- package/types/triggerledger.d.ts +35 -0
- package/types/userrun.d.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,114 @@ All notable changes to bareloop are documented here. Format:
|
|
|
5
5
|
[SemVer](https://semver.org/spec/v2.0.0.html). Pre-1.0: **minor** = a ladder rung or
|
|
6
6
|
feature lands, **patch** = docs, fixes, scaffolding.
|
|
7
7
|
|
|
8
|
+
## [0.43.0] - 2026-10-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **A job can be scheduled from the panel (T, Part 3 of 8; panel-only, nothing fires yet).** A signed spec may carry a `trigger` (`daily`, `weekdays`, `weekly`, `new-file` folder, `file-updated` file), set only by a two-click, $0 door (`POST /api/author/schedule-prepare`, then `POST /api/author/schedule` with the hash it showed; `when: null` cancels and is never refused). It needs a real green run of the job's current workflow (a rubric job also needs an honoured Accept). Files live under `~/.config/bareloop/triggers/<job>/` (`schedule.json`, one `ledger.jsonl` whose rows all carry the signature). The agent never writes a trigger: a draft that carries one is refused by name. `workflowKey` ignores `trigger`; `cadence` is no longer required (an old spec keeps it, still valid, same hash) and new specs no longer write it.
|
|
13
|
+
- **One starter for a scheduled run (T, Part 4 of 8; still no timer).** A scheduled fire goes through the same session start and the same `signRun` as a Reuse the person clicked (headless sessions kept apart from the person's chat session; `--approve` is the hash derived by code; a scheduled spawn passes `--review-door`). Fire order: busy, monthly, `wouldAccept`, started; each outcome is one row, told once per slot-or-file, outcome and reason. A busy, monthly or refused fire consumes nothing, so it retries when things are back up; the `fire`/`ran` row is written only after every check said yes, and a launch error after it writes a `released` row (at most 3 times).
|
|
14
|
+
- **The panel's scheduler (T, Part 5 of 8).** While the panel is up a 500 ms timer (unref'd, stopped when the server closes) arms each signed schedule, fires a due clock slot or a queued file through the one starter, and writes the `arrived` rows; poll only, never `fs.watch`. A run of the job is busy only while its pid is alive AND the process started no later than 5 s after its run row was written (a recycled pid is not that run). One job's failure is written once and never stops the others. Missed slots still collapse to one catch-up.
|
|
15
|
+
- **The words of a schedule (T, Part 6 of 8).** `src/schedulewords.js` is the one owner of every schedule sentence: the next time (`today 15:00`, `tomorrow 09:00`), `next in 20m`, the NEXT line (a run waiting for your Accept does not hold the next one; the month used up reads as a red `Paused: ...` with two-decimal money and a typed red flag), the catch-up note and the edit-clears sentence. The page paints strings and works out nothing.
|
|
16
|
+
- **Schedule run, from the panel (T, Part 7 of 8).** The Run tab's last button is `Schedule run` (a job with a good run) or `Cancel / modify schedule` (a scheduled job, on every run of it, also while one is live); a job with no good run has neither. It starts and signs nothing: it opens the Chat card in schedule mode with a `When` field (By hand / Every day / Weekdays / Every <day> / When <file> is updated / When a new file arrives in <folder>). One main button: `Sign & run` while When is unchanged, `Sign schedule` / `Cancel schedule` when changed, `Confirm: <words>` after click 1 ($0, signs nothing), and after click 2 the card clears to the empty New job card with the button dead for 1.5 s (no Run invitation after a sign). Runs list gets a `Scheduled` section after Needs you (soonest first, a click opens the job's newest run). ENDED / NEXT moved from the Audit tab to the Run tab, with a SCHEDULED / NEXT twin directly below it (NEXT red when the month is used up) and a note on why a run started and when it was a catch-up. Editing a job never clears its schedule (see Changed: one-door Reuse). Every schedule word is one table (`SCHEDULE_WORDS`) the server sends.
|
|
17
|
+
- **A run waiting at its review door shows `Accept` in Needs you** (never ages out).
|
|
18
|
+
- **Accept, from the panel (T, Part 8 of 8).** A run waiting at its review door gets an `Accept` button on the Run tab (right after Resume, before Edit in chat), shown only when the server says a door is open. It runs the engine's own door path (`run-u --spec --door --decide accept --approve`): the mechanical stages re-run, a red is `door-accept-red`, the verdict is untouched; the engine's own words are shown on a refusal. A scheduled run's green is held (not offered by Reuse, `green-held`) until it is accepted.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **The Runs list search and run cards follow fwdloop M11 am5 and am6 (PANEL-BUILD.md P9 v2 section O; replaces the "one newest older match past the 7" rule).** The search splits at spaces and every word must match; `run-2` matches only run-2 (never run-20); `MM/DD` or `YYYY-MM-DD` matches the runs that started that local day (a part date such as `10/1` matches nothing); while a search or chip is on, a job's older-runs fold shows only matching runs, newest first, at most 7, plus the selected run. The line after the 7 reads `(and k older · <oldest> to <newest> of those k · search above)` or, while searching, `(and k more matches · add a day or a run)`; the search box reads `job, run-23, or a day 10/12`. Line 1 of every run card, and of the Running now and Scheduled cards, now sets the date, model, time, cost and next-run text in the same size and baseline as the job and run name (`run-2 · 10/12 13:46 · deepseek-flash`), not in the small raised faint span.
|
|
23
|
+
- **The Runs search takes any day form, ignores brackets and says it plainer (fwdloop M11 am7; PANEL-BUILD.md section O am7).** `10/13`, `10-13` and `2026-10-13` mean the same local day; `aging-live-4 (run-2)` finds what `aging-live-4 run-2` finds; the line after the 7 gives the hidden runs' real oldest-to-newest days (`4 more older runs, 10/11 to 10/13. Search to find one.`, or `6 more runs on 10/11. ...`, or `3 more match. Add a date or run number to narrow it.` while searching); the search box reads `search: job name, run-23, or a date 10/13`. Replaces am5 item 5's words.
|
|
24
|
+
- **A scheduled fire refused with `install-needed` backs off 1, 2, 3, 5, 8, 13, 21 minutes, then stops and waits for you (PANEL-BUILD.md T addendum item 11, RULED 2026-10-15).** It used to redo the copy and `npm ci` on every 500 ms tick; the first fix was a flat 1 minute. Now each consecutive refusal of a job waits the next Fibonacci step (the count is in memory and starts over after a start, a cancel or a re-sign); the next step that would pass 60 minutes in total is not taken: ONE `gave-up` row is written and the schedule pauses (read from the ledger, so a restart does not resume it). No clock slot fires and no queued file runs while paused; new files still queue. The job shows in Runs list Needs you with the pill `Check` (reword is one edit in `SCHEDULE_WORDS`), and the run page's NEXT is red: `Stopped trying after 60 minutes: <words>. Fix it, then Cancel / modify schedule to start again.` Re-signing through Cancel / modify schedule, or cancelling, clears it. During a wait NEXT reads `Trying again in N minutes.`
|
|
25
|
+
- **The Runs search box reads `job, run-23, or date 10/13` (fwdloop M11 am8; PANEL-BUILD.md section O am8).** am7's longer words clipped at 320 px; the new ones fit, and a test pins the words and a 32-character limit.
|
|
26
|
+
- **The Runs search line after the 7 shows the hidden matches' real days (fwdloop M11 am9; PANEL-BUILD.md section O am9).** While searching: `5 more matches, 10/11 to 10/12. Add a date or run number to narrow it.`, or `50 more matches on 10/09. Add a run number to narrow it.`; a hidden match with no date drops the date part. Replaces am7 item 4's `k more match.` words.
|
|
27
|
+
- **Runs-list card money names the drafting share.** The short form reads `$0.22 (drafting <$0.01)` (was `$0.22 (<$0.01)`, the bracket's meaning unclear); a floor reads `(drafting at least $X)`. The long form `($X drafting)` is unchanged.
|
|
28
|
+
- **The panel's Runs list is rebuilt in fwdloop's shape (P9 v2).** One list, top to bottom: Running now (live runs, the wall counting up in place), Needs you (a stopped run the engine would resume, with a `Resume` label, for 7 days), the filters, Jobs (run in the last 30 days, newest activity first) and a folded `quiet jobs`. A job card shows `job · k runs · check type` and its newest run; its runs sit under it, with the older ones behind `older runs (k)` (7 shown, then "use search"). The Workflows / History switch and the flat History list are gone.
|
|
29
|
+
- **Search reads the whole visible row** (job, run-N, run id, model, status word, reason, date, cost, wall, check type); a matching run is marked with a blue bar, never selected, and the fold opens on it. The search box placeholder is shorter.
|
|
30
|
+
- **`GET /api/runs` now returns `{runs, jobs, limits}`.** The server decides live, needs-you, each job's newest run, the quiet split and the limits; the page works out nothing.
|
|
31
|
+
|
|
32
|
+
- **Reuse workflow is ONE door on every run (RULED 2026-10-15).** Every box opens; the card's job boxes pick the road. A green or already-green run with no job box changed signs its plan verbatim under its own name with no drafting; a changed job drafts a NEW workflow under `<base>-N` (a name already used by another job is refused in plain words). Edit in chat and the locked-box refusal are gone, and an already-green run now counts as green for Reuse and the picker.
|
|
33
|
+
- **Remove from Needs you (`POST /api/runs/:runid/needs-remove`).** A stopped run's card leaves Needs you with one `needs-removed` row in `runs.jsonl`; its status, list row, Resume and money stay, and a later stop brings it back.
|
|
34
|
+
- **The SCHEDULE line says why, and only real problems are red.** Line 2 says a run is waiting for your Accept, and `busy` reads "still working"; before the first fire it says no run yet, and an unarmed schedule ends `next: not armed yet`. Only a refused try, the give-up and a used-up month are red, and the month-used-up red shows only in the month it happened; busy and the install back-off are not red.
|
|
35
|
+
- **A run's name reads `job · run-N` in Running now, Needs you and Scheduled,** and the Scheduled and gave-up cards carry the run's number from the server.
|
|
36
|
+
- **A stopped run is grey, not red.** The `[■]` sign, the step-map chip and the Run tab frame all use the same grey as `[·]` (you stopped it, it did not fail); a schedule problem on the run still wins red.
|
|
37
|
+
- **A sign blinks only while the run is running or waiting for your Accept;** every other sign rests (reduced motion keeps the sign).
|
|
38
|
+
- **The Audit tab of a scheduled job has its own folded Schedule section at the top:** this run's start, and the tries that started nothing (all signings, newest 7), in the same rounds-table shape as the drafting and step sections.
|
|
39
|
+
- **The Run tab has one summary block (ENDED dated, NEXT, SCHEDULE with last and next fire; red on a problem), and the Job tab has Started and Schedule rows.** Editing a job never clears a schedule. Runs-list rows say `step N of M` while running.
|
|
40
|
+
- **Resume and Accept answer in plain words, never raw engine output (RULED 2026-10-15/16).** A known engine sentence or a fixed plain sentence with the log path replaces raw output (`engineRefusalWords`); Resume and Accept on a run whose saved job fails today's checks say so in plain words and refuse at $0 before spawning anything. An Accept refused by its final checks names each failed check by its plain question, never the stage id.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- **Schedule run and Cancel / modify schedule never start a run (live defect, run mv9euoam).** When only Destination, Inputs or a cap changed (or nothing changed), the card's main button stayed the Reuse card's `Sign & run` and started a hand run without touching the schedule. The schedule card now has no run path: a change in When, Inputs, Destination or a cap makes the button `Sign schedule` (then `Confirm`, the $0 two-click door); unchanged is a disabled `Sign schedule` with the "nothing changed" words.
|
|
45
|
+
- **A refused scheduled fire now shows on the open run page.** The page stopped refetching a run once it was no longer live, so the SCHEDULED / NEXT block of a finished run stayed as first painted and the red NEXT ("The last try did not start: ...") the server already sent never appeared. A finished run of a scheduled job is now refetched on the runs-list cadence (10 s).
|
|
46
|
+
- **A scheduled start that is refused leaves no session folder behind, and a refusing child cannot loop.** `wouldAccept` now runs the session's own Destination check and the good-run check before any session exists; any session the starter made for a start that is then refused (the copy refused a symlink, packages missing, the saved copy no longer hashing to what was signed, a failed spawn) is removed by the starter, and a child that exits non-zero within 1.5 s with no row in the run list counts as a release toward `MAX_RELEASES`, so the retries and the rows stay bounded. The live failure wrote 237 folders at 500 ms.
|
|
47
|
+
- **A plain-files job can be scheduled and reused with a real Destination (rubric-2 schedule refused at its first fire).** The Destination of a plain-files rubric job is an absolute folder that the signed spec never holds (its fence is `output/` inside the run's tree), but the Reuse and Schedule cards opened with that fence and `buildReuseSpec` wrote whatever was typed into it, so a schedule signed fine and its fire was refused ("Destination must be the full path of a folder"). The card now opens with the folder the origin run delivered to, an absolute Destination stays out of the spec (it rides on the schedule and in the hash both clicks bind), and ONE check (`startDestinationProblem`) runs at both schedule clicks, at the HTTP start (before any session folder exists) and in a scheduled fire's `wouldAccept`.
|
|
48
|
+
- **A rubric job signed without an explicit `judge` can be scheduled and reused.** `workflowKey` now hashes the RESOLVED judge of a soft-green spec (the runner's `resolveJobJudge`), so a missing judge and its pinned default (what Schedule run and Reuse sign) are one workflow; an explicitly different judge is another, and deterministic keys are unchanged. Before, Schedule run (and Reuse) of such a job was refused with "not the one that had the good run" after an honoured Accept (mv76m82c). Soft-green keys differ from before, so a schedule saved by an earlier build for a judge-less rubric job needs re-signing.
|
|
49
|
+
- **A replan answer cut off at its length limit is no longer reported as "The model provider failed" (RULED 2026-10-16, F229, run mv9gm796).** It is a bounded failed attempt: the cut round is booked, the half-answer is never validated and is handed to the one redraft as a plain-words red (a cut redraft, even after a bad first answer, ends the run), and the run ends with the plain sentence "The replan answer was cut off at its length limit, so no new plan came back." It is no longer in the resumable provider-red set. Scout and the first draft are unchanged. Test-proven only.
|
|
50
|
+
- **Reuse workflow on a saved job that makes the validator throw answers the plain "made by an older bareloop" words** instead of a raw `internal error` 500.
|
|
51
|
+
- **A scheduled start's `lateMs` and `startedAt` use the same clock the scheduler uses** (they read the wall clock beside an injected scheduler clock).
|
|
52
|
+
- **The `signed` schedule-ledger row no longer carries the unread `originRunid` field** (the Audit reads `schedule.json` and `trigger.json`).
|
|
53
|
+
|
|
54
|
+
## [0.42.0] - 2026-10-14
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **Rubric jobs on local files, no judge yet (Part 1a).** A Rubric job whose first Input is the full path of a text file (`label: /abs/file`) no longer stops at "not a code project". `proveRubricInputs` (`src/source.js`) freezes each file into the hidden-git tree and records `inputs: [{ label, rel, sha256 }]` in `source.json`; a web address, search line, relative path, symlink, env file, over-5-MiB or non-UTF-8 file is refused at $0 in plain words ("line N: web inputs (URLs, search) arrive in a later build. Nothing spent."). There is no scout and no confirm turn: the worker's goal is written by code from the numbered job lines (`src/rubricstep.js`).
|
|
59
|
+
- **One output per job line.** The worker writes `output/steps/<n>.json` (`{ text, cites: { <label>: [{ claim, quote }] } }`); `output/steps/` is delivered with the output.
|
|
60
|
+
- **New close kind `rubric-step`** (text genre only, `closeDecl.lang: "text"`; a code job never sees it): one stage per job line, graded by bareguard's rubric primitive rebuilt every run from the input shas the source door recorded. Checks: the step file exists and is a JSON object (`happened`), `text` present, `cited` per input the step cites, and `maxWords` / `minWords` / `maxLines` / `sections` / `sectionWords` on `text`. A missing or cut-off file is red; a changed input, an unreadable record or a rubric that cannot be built is an instrument stop (97).
|
|
61
|
+
- **Every part of a job line is sorted at drafting** (countable, judged, guard, none). A judged part ("this rule needs the judge, which arrives in the next build. Nothing spent."), an unsortable part, a guard with no web input, contradicting limits (both parts and numbers named), a stage on the wrong line and a quote from an unknown input are plain-words reds; the drafting and signing stops print them whole.
|
|
62
|
+
- Signing such a job makes zero judge-round records (the calibration gate runs only for a judged stage).
|
|
63
|
+
- **The confirm turn is back for these jobs** (form -> confirm turn -> sign, scout still off). Its protections come from code: the text genre's guard, the write fence and one line per job line saying its step file must exist, be complete JSON and carry text; the model is told what a check can be here and shown the same list as already covered.
|
|
64
|
+
- **A red count for the fix-loop governor.** A red `rubric-step` stage reports failing claims plus failing checks, lower is better, through the same `closeGrade` path numeric stages use.
|
|
65
|
+
- **The panel words a rubric-step check** as "line N — does its output pass the line's checks?" instead of the raw stage name.
|
|
66
|
+
- **Worker brief:** copy every number, time and price in a claim exactly as the input writes it.
|
|
67
|
+
- **The panel topbar shows the version:** `bareloop · v<version> · automate a job, verified`, read from bareloop's own package.json when the page is served (never written into the HTML).
|
|
68
|
+
- **Each `~` guardrail is shown on its own marked row** under its numbered job line on the plan card and the Job tab (fwdloop's shape).
|
|
69
|
+
- **A refused drafting try records why** in `draft-log.jsonl` (`try-refused`) and on the drafting step's progress line.
|
|
70
|
+
- **Required facts (Part 1b): a job line that finds, lists or looks something up is checked by what the person asked for, not what the worker chose to write.** The drafter turns the line and its `~` rules into a signed list of facts (name, shape money / number / date / time / phrase / text, a rule only where asked, the input they come from); the worker's file for that line is `{ results: [{ quote, values }] }` with no free text, and bareguard's `cited` + `decides` checks every result in code: the quote is really in the frozen input, every value is really in the quote (compared by shape), every value meets its rule. The claim text is written by code; "N results" is a count code checks. (`src/facts.js`, `src/rubricstep.js`)
|
|
71
|
+
- **Records. (Removed in Part 1c, below: no record cutting.)** The drafter names where a record opens and where the quoted slice ends; code splits the input and counts the records. The drafter reads the input at drafting (a big one as a said-so sample: the first 15000 characters, cut at a line end, shown on the card).
|
|
72
|
+
- **Three early tests at drafting, on the real input (removed in Part 1c, below: code now only proves the words are in the source),** before a fact check can be signed (`src/factcheck.js`): every word a check depends on is really in the input; the check finds its value in each record (count shown); a rule has one value per record or an anchor; a value changed by code makes the check go red; the pass / fail split of a rule is shown. A run repeats the words / found / one-per-record tests on the source it reads and stops in plain words if it no longer fits.
|
|
73
|
+
- **No silently dropped rule.** Every `~` rule must become a check (its number in a part's `rules`) or be listed under Not checked (`refused` entries may now name a `rule`); a number the AI made out of the person's words is marked "AI's proposal — check it", and an AI time limit must be a range.
|
|
74
|
+
- **Prose after facts.** Every number in a text step must appear in a checked quote of an earlier fact line (bareguard `numbersInQuote`); the card says "what the text says" is not checked and is judged at Accept.
|
|
75
|
+
- **Plan card and worker brief.** One code-worded row per fact (with the early-test evidence) on the confirm card and the Job tab, covered by the sign-time shown == run check; the worker's goal briefs each line by what its stage checks.
|
|
76
|
+
- **A refused drafting try keeps its declaration (Part 1b live fix A3).** The refused proposal rides `author-try-failed` and the panel writes it to `draft-log.jsonl` beside `try-refused` (secrets masked, cut at 60000 characters with `declarationCut`), so a failed draft can be read and replayed at $0.
|
|
77
|
+
- **One rulebook for the confirm step and the drafter (B1).** `RUBRIC_RULES` is the single text both `RUBRIC_CONFIRM_NOTE` and the drafter brief print: a limit the person wrote is a checkable rule; a number made from their words is the AI's proposal and a range; names, labels and units are the input's own words, and a code like `SFO` the person did not write is never added.
|
|
78
|
+
- **The drafter is shown which fact shape takes which fields (B2).** `factShapeGuide` is read off bareguard's `rubricVocabulary` (`factShapeFields` throws at load if bareguard's wording changes) and carried in `REQUIRED_FACTS_BRIEF`, which also says one stage holds all of a job line's facts.
|
|
79
|
+
- **The look, then say what can be checked (Part 1c, docs/product/ITEM33-BUILD.md; PRD item 33 RF18-RF22).** Drafting no longer grades and no longer cuts records. Before the drafter writes a check, a read-only, bounded look (`runInputLook`, `src/authorscout.js`; tools in `src/inputlook.js`) searches the person's frozen input text, reads the spots it finds, and reports per job line the facts it can check - name, shape, a label, one example value it saw - and what it could not find. Fixed grant (search and read over in-memory text, nothing to write), `LOOK_ROUNDS` 8, a reserved final round whose only tool is the strict answer form (`report_look`; never free prose with JSON inside), spend metered into the drafting book like the repo scout's. Every place, date or name in the job line is a fact too, bound to the label before it (a direction is `Leaves` / `arrives at` as a phrase fact); prose lines and their `~` limits are not looked up. The drafter is shown the verified report.
|
|
80
|
+
- **Labels are picked, never typed (`src/labelscan.js`).** Code scans the source at $0 and lists, per shape (money, number, date, time, text), the words right before each value with how often they occur (`Departure time:` x43, `From` x44, `Leaves`, `arrives at` on the saved flights page; `## [` and `] -` on this changelog). The look and the drafter pick a label only from that list, by id or exact words; one scan for every page or file, no per-site table.
|
|
81
|
+
- **Code proves the words are in the source (`src/factwords.js`).** Every anchor must be on the input's list for its shape, and every `until`, phrase and unit must be in the source once, compared white-space blind (Google writes U+202F before AM/PM). A word not found is a red naming the word and the input (`rubric-fact-label-off-list`, `rubric-fact-word-missing`, `rubric-fact-unread`); the look's phrase facts are proved by the same counter.
|
|
82
|
+
- **Web addresses as inputs (`src/pagefetch.js`, `src/source.js`).** An Inputs line may be `label: https://...`. barebrowse (`^0.21.0`, now a production dependency) fetches it read only (`connect` with `incognito`, `blockPrivateNetwork`; `goto`, then a bounded wait for the network to go quiet so the page's own scripts finish filling the results, then an `act` snapshot); an empty, near-empty (under 200 characters), header-and-menus-only (nothing outside the banner / navigation landmarks), bot-challenged or unreachable page is a plain-words stop. Drafting fetches it once for the look; run start (`refreezeUrlInputs`, called from `run-u --spec`) fetches it again, writes that text into the prepared tree as the run's frozen input and commits it as the new seed before the worker starts, so the close checks quotes against the run's copy and the worker reads it, never the live page. A resume keeps the copy its first start froze.
|
|
83
|
+
- **Ask at once about a `~` rule the look could not capture (I1).** When the look names a `~` rule whose words or meaning are not in the source, the person is asked about that rule, by line and rule, before the plan and the draft (the existing question gate: answer required, no skip, the answer becomes a `~` rule on that line; at most `MAX_QUESTIONS`). Any other drafting failure of a rubric job on files asks nothing piecemeal: the stop says to review the whole job and submit it again, with the reasons in plain words.
|
|
84
|
+
|
|
85
|
+
### Fixed
|
|
86
|
+
|
|
87
|
+
- **A missing earlier step file is a normal red; a rubric job's broken check no longer blames install/build/test commands (Part 1c M2).** When a fact line ties its numbers to an earlier line's answer and that line's step file is not there yet, the grade says "line N's answer is not there yet" instead of raising an instrument fault; a `broken-close` on a rubric job reads "A check of the close could not run" instead of the code-job wording about install, build and test commands.
|
|
88
|
+
- **After Sign & run the Runs list is refreshed until the new run is listed (Part 1c M3).** The list re-fetched only every 10 seconds and the runner writes its row a few seconds after the click, so a signed run showed only after a page refresh; the panel now forces bounded refreshes until a run not listed at the click appears (as a resume already did).
|
|
89
|
+
- **A header-only page stops before the look, and an empty input is never asked about (Part 1c L2).** A fetched page whose text outside the banner, navigation and footer landmarks is under 100 characters is refused in plain words (a 719-character fetch of a site's header and sign-in used to pass the 200-character floor); an input with no content makes the survey absent before the look and before any question, so nothing is asked and nothing is spent.
|
|
90
|
+
- **A fact's words must repeat as often as results were asked for (Part 1c M1, M4).** On a line asking for N results, an anchor label (M1) or a "must say" phrase (M4) that the input holds fewer than N times sits in no single result (a sort tab, a page header), so no one-result quote can hold it. `checkFactWords` refuses it at $0 in plain words naming the words, the input, the count and N (`rubric-fact-label-rare`, `rubric-fact-phrase-rare`); count 1 or no count keeps a once-only one legal. The one rulebook (`RUBRIC_RULES`) tells the drafter. Live runs mv73swnx and mv76jhj1.
|
|
91
|
+
- **Plain words for every rubric red (B3).** `rubric-step-twice` and every `rubric-*` code read as a whole sentence on the proposal door, the signing door and the stop text; `invalid-value` / `missing-field` on a rubric-step's parameters say where (stage, part, fact, field). A test reads the rubric red codes from source and fails on any unmapped one.
|
|
92
|
+
- **The long-record refusal says what to do (A1; removed with record cutting in Part 1c).** `rubric-fact-record-long` names the record, its length, the end marker given (or "no end given") and an example taken from that record.
|
|
93
|
+
- **An end marker the records do not hold is its own red (A2; removed with record cutting in Part 1c).** `rubric-fact-record-end` names the marker and how many records lack it; before, a partial miss fell back to the whole record and read as "too long". A marker that is the first thing after a record's start (it opens the next record too) counts as found.
|
|
94
|
+
- **Drafting wording.** The drafting line says `drafting (retry N of M)` only after a try that was actually refused, and `drafting (look N of M)` after one that passed (the loop drafts again after a passed try too). A refused try's reason, and the drafting stop texts, no longer end "Nothing spent." — a drafting try always paid for its model call.
|
|
95
|
+
- **The plan card showed the drafter's prose (run mv5e1ktb).** The confirm turn's bubble stayed above the code-worded card and was the one read. After drafting the earlier plan bubbles are dropped, so the card is the signed close's own sentences. "at most 1 lines" now reads "1 line" (and "1 word").
|
|
96
|
+
- **The review door's held line said "rendered by the judged floor" for a rubric close where no judge ran.** Wording only: the hold is by verdict class and is unchanged.
|
|
97
|
+
|
|
98
|
+
- **A check shown at the confirm turn could be dropped from the signed close without a word (run mv5bk400).** The composer put "lists 3 releases" in the close's notes and wrote no stage for it. Now `minClaims` / `maxClaims` count the claims listed under an input (the code counts, bareguard's `min`/`max` compares), parts say which confirmed checks they carry out (`covers`), the plan card after drafting is built from the signed close itself, every confirmed check no part carries out is moved to Not checked in code, and signing refuses when the checks shown differ from the checks of the close.
|
|
99
|
+
- **Wrong labels on a rubric job on files:** the close carries genre `TEXT` (not `TYPES`), its records carry no `judgedCount` (no judge ran), and the progress list says "copying input files" / "reading input files" instead of "making worktree" / "reading repo".
|
|
100
|
+
- **The panel could not start a rubric job with two or more local-file Inputs:** `POST /api/author/start` asked the repo proof of every card and reded line 1 ("is not a folder"). A rubric card whose first input is a local file now skips it; the session proves each line.
|
|
101
|
+
|
|
102
|
+
- **The panel shows a refused Start (Part 1c item f).** The $0 refusal that `/api/author/start` returns (an Inputs line, a Destination) was drawn as a faint 11px hint under the cap fields and never scrolled into view, so it read as nothing happening; it is now red, 13px and brought into view, like the progress list's failure line. The failed-draft refusal of a pasted web address that cannot be read no longer ends in two full stops.
|
|
103
|
+
- **A fact line's `~` rule is carried by its fact part, and `covers` stays on the part (Part 1c L4).** Live drafts of the flights job (panel session smv710w8c3wdh, 3 of 3 tries) were refused on form: `covers` written on every fact, and "not a red-eye" / "nonstop" sent as a judged or unsortable part beside the fact part. The one rulebook (`RUBRIC_RULES`, `REQUIRED_FACTS_BRIEF`) now says a limit the AI turns into a number goes on the time the input holds, as a `between` range marked AI's proposal, inside the fact part; a rule it cannot express goes in `refused`, never a part; and the refusals (`src/facts.js`, `src/rubricstep.js`) say where each belongs.
|
|
104
|
+
- **The plan card keeps the person's words (Part 1c L3).** Code settles it before anything is validated or signed (`settlePersonsWords`, `src/rubricstep.js`): a limit made only of the person's own numbers is not marked AI's proposal; a time window the AI proposed is replaced by the one explicit positive range the person wrote or answered ("between 07:00 and 23:00"; a "not between ..." rule is never read as a range); a `~` rule that a part carries is not also listed under Not checked. The look carries a rule it can number from its own knowledge ("not a red-eye") as a fact with `forRule` marked AI's proposal instead of reporting it not found, so it is not asked; and the early question's reason is cut only at a sentence end (`wholeSentences`), never mid-sentence.
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
|
|
108
|
+
- **The plan card legend and fold wording** (chat plan card and Job tab): the legend reads "Done when every › check passes" and the mandatory-guards fold is "Always checked" (was "= the approved plan" and "Always on"). Words only.
|
|
109
|
+
|
|
110
|
+
- **barebrowse `^0.21.0` is a production dependency** (bare suite, Node 22). bare-agent keeps its own optional nested `barebrowse ^0.5.0`; the two do not conflict in a clean install.
|
|
111
|
+
- **bareguard `^0.21.0` -> `^0.23.0`** (`cited` decides with typed values, bind-only and `text` decides, anchors match on word edges only, a `cited` claim carrying `values` with no `decides` is red).
|
|
112
|
+
- **bareguard `^0.23.0` -> `^0.24.0`** (a money fact's `currency` may be one symbol or a list of 1-3 and `unit` one string or a list of 1-10, at least one; tokens with the same amount count as one price; a revoked-Proxy claim value is red `claim-value-malformed`; `rubricVocabulary` currency/unit descriptors carry `list:{max}`). bareloop still sends one `unit` or one `currency` per money fact (`src/facts.js`), so the lists are available but not yet used.
|
|
113
|
+
- **`~ PASS:` / `~ FAIL:` examples are required of a rubric job only when it holds a judged part** (`jobBlockFitsCheck(lines, rubric, { judged })`, hamr's ruling P1=B). A rubric on a local file holds none in this build; a rubric on a repo still needs them.
|
|
114
|
+
- **Node 22 or newer is required** (`engines.node` `>=22`, CI on Node 22). barebrowse, which the coming rubric `search` step drives, needs Node 22; Node 20 is past end-of-life.
|
|
115
|
+
|
|
8
116
|
## [0.41.0] - 2026-10-12
|
|
9
117
|
|
|
10
118
|
### Added
|
|
@@ -47,7 +155,7 @@ feature lands, **patch** = docs, fixes, scaffolding.
|
|
|
47
155
|
- **Try-3 questions.** On the last drafting try, after two failed ones (validation red, seed-read failure or vacuous close, or an output cut-off), the drafter may ask up to 2 questions, each naming a job line. The panel asks each through the ordinary ask path (answer required, no Skip), adds the answer word for word as a `~` rule on that line, and drafts once more with no asking. Off unless the caller wires an asker (the CLI never does).
|
|
48
156
|
- **Thinking switch for drafting and the judge.** `thinkingMode` ('on' | 'off', anything else throws) is asked of bare-agent; `off` is sent only for models bare-agent's `thinkingSupport` says are switchable, and nothing is sent for a not-switchable or unverified model. The draft log records `thinking` (off / on / not-switchable / unverified) and bare-agent's `thinkingApplied` verdict per call.
|
|
49
157
|
- **Panel.**
|
|
50
|
-
- One shared plan renderer draws the chat bubble and the Job tab: numbered lines, rules, checks under the line they serve, with *Not checked* and *Always
|
|
158
|
+
- One shared plan renderer draws the chat bubble and the Job tab: numbered lines, rules, checks under the line they serve, with *Not checked* and *Always checked* folds; an "AI's summary" row, and an "AI's open questions" row (each question its own item, then "Revise if you need to.") shown only when there are questions.
|
|
51
159
|
- The Job tab runs The job, Inputs, Destination, Tools, Cap, Signed, with the job at full height; older jobs show an older-job note and keep their goal as line 1.
|
|
52
160
|
- The job boxes grow to 8 lines then scroll, with the number gutter scrolling alongside; the left pane widens to 630px from 1200px wide.
|
|
53
161
|
- The drafting list opens with a code-owned *thinking* row; if the model reports it thought anyway after `off` was sent, it reads "thinking on (the model thought anyway)".
|
package/README.md
CHANGED
|
@@ -18,33 +18,25 @@
|
|
|
18
18
|
|
|
19
19
|
**"Automate this job — I don't know the best workflow."**
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
itself, as a constrained and validated config rather than freeform code. Every run executes
|
|
23
|
-
under an outer gate the agent cannot reach or argue with. And the workflow gets better across
|
|
24
|
-
runs, because what actually worked is kept with receipts and carried into the next run.
|
|
21
|
+
The best workflow is the shortest proven path that gets the job done, and that heals itself when a different model works a different way.
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
done. Three of the strongest results in long-running agent work landed on the same answer
|
|
28
|
-
independently: the ralph loop, code machine MCP, and recursive language models all trust the
|
|
29
|
-
agent to do the work and verify it with something the agent cannot reach. Trust, but verify.
|
|
23
|
+
bareloop finds that path for you. Write your problem in plain language, as steps. Under each step, say what counts as success and what it must never do. The agent builds the bridge it will walk on, and the run is green only when the output passes checks the agent never touches.
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
job and what *done* means for it. The agent handles the rest, under verification it never
|
|
33
|
-
gets to touch.
|
|
25
|
+
The harness (budget, time, what it may touch) and the judge both sit **outside** the agent loop. The agent can't judge itself. Every run is recorded, so you can audit it and replay it at $0 and see exactly what happened.
|
|
34
26
|
|
|
35
|
-
## Two kinds of
|
|
27
|
+
## Two kinds of jobs
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
**Deterministic (code)**: code decides. Tests pass, the linter is quiet, the build exits clean. If no check exists yet, say what you need and the agent builds that check first.
|
|
30
|
+
> *"Fix the lint errors."* · *"Add doc comments to every function."* · *"Split this 3,000-line file without breaking a test."*
|
|
39
31
|
|
|
40
|
-
**
|
|
41
|
-
|
|
42
|
-
> *"Fix the failing suite."* · *"Migrate this package to strict types."*
|
|
32
|
+
**Rubric (research)**: it searches, collects, compares and hands you a report where every fact is quoted from a saved copy of its source. A judge only points at the quotes, and fixed rules decide.
|
|
33
|
+
> *"Find flights LAX→SFO under $400, nonstop, no red-eye."* · *"Compare these 5 laptops on price, weight and warranty."*
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
35
|
+
## What it won't do
|
|
36
|
+
|
|
37
|
+
bareloop reads, searches and writes. It never acts on the world: no paying, booking, sending forms or posting. When a job needs a person partway through, or needs to *do* something, like book the flight bareloop found, that's **[fwdloop](https://npmjs.com/package/fwdloop)**.
|
|
38
|
+
|
|
39
|
+
**Small tools, all local.** Built on the bare suite (bareagent, bareguard, litectx), always LLM-agnostic, always on your machine, with a simple panel UI.
|
|
48
40
|
|
|
49
41
|
## You have the last word
|
|
50
42
|
|
package/bareloop.context.md
CHANGED
|
@@ -153,7 +153,7 @@ minting claim, or the shell-owned retry cap — all unknown-field reds.
|
|
|
153
153
|
| `description` | non-empty string | |
|
|
154
154
|
| `provider` | `anthropic-api` \| `openai-api` \| `gemini-api` \| `clipipe-subscription` | menu (`PROVIDERS`): an API SHAPE (a wire protocol), part of the lineage key by definition. Only `anthropic-api` is guaranteed (F48); the shapes go through the provider factory (`src/providers.js`) — see "Worker surface" above for the `baseUrl` field and the row's old-max_tokens tick; `gemini-api` (PRD item 31.3) is ADMITTED-PENDING-PROBE — zero runs, and every launch says so (`PROBE_STATUS`/`probeWarningLines`); `clipipe-subscription` drives the worker natively and needs `opts.nativeProvider` |
|
|
155
155
|
| `conditions` | `{ providerPath?, closeVerbosity?, taskFraming?, scaffold? }` | declared keys only, string values — the environment label (consumed by the N3 lineage key; recorded on spines from run one) |
|
|
156
|
-
| `cadence` | `{ unit: hour\|day\|week, every: 1..30 }` | validated
|
|
156
|
+
| `cadence` | `{ unit: hour\|day\|week, every: 1..30 }` | still validated if present but IGNORED: it starts nothing (an old signed spec keeps it, never rewritten). Scheduling is the `trigger` field, panel-only (see "Triggers" below) |
|
|
157
157
|
| `budgetUsd` | finite `0 < n <= shell cap` | ceiling chain: workflow ≤ job ≤ shell — each layer may tighten, never exceed |
|
|
158
158
|
| `maxWallMs` | optional integer ms `>= MIN_WALL_MS` (one close timeout) | the run's wall clock. **NO DEFAULT, by ruling** — absent means time-unbounded *by explicit operator choice*, never by fallback (F45: a defaulted cap is a silent second ceiling). Enforcement is a BETWEEN-ROUND deadline, so the honest worst case is `maxWallMs + closeStages × closeTimeoutMs` — every stage of a staged close gets the FULL timeout — and all three numbers are reported (`loop.stop()` cannot cut an in-flight call — F61 measured 500ms→4,018ms). The `MIN_WALL_MS` floor is a ONE-stage, un-autoset-default number, so a spec with many stages OR a raised/autoset `closeTimeoutMs` can validate while its overshoot dwarfs its cap: `runPlan` adds a RUN-START check (PRD item 27/M3) that refuses `wall-under-close-timeout` at $0 once the EFFECTIVE per-stage ceiling is known (autoset or signed), rather than a silent clamp. Operator-only, tighten-only; adding or changing it changes the spec hash |
|
|
159
159
|
| `closeTimeoutMs` | optional integer ms `>= CLOSE_TIMEOUT_FLOOR_MS` (120,000 — hamr's arbiter floor, 2026-09-07) | PRD item 27/M3's SIGNED override for the close's own per-stage wall-clock cap. Absent (the normal case): the run autosets it from a $0 seed timing pass — `max(FLOOR, K × slowest measured stage)`, `K = 5` (hamr, 2026-09-07: "2 and 5 are fine"). Present: the operator's own number wins outright and may legally sit ABOVE *or below* the autoset estimate (the rates-passthrough shape, F113 — never tighten-only). Arbiter territory exactly like `budgetUsd`/`maxWallMs`/`close[].sha256`: the authoring pipeline's `assembleSpec` refuses a draft carrying it |
|
|
@@ -721,7 +721,7 @@ attempt PLUS its close. Unpriced is never free: a null cost is a `pricing-red` s
|
|
|
721
721
|
never retried, and the ladder is otherwise ONE retry (`JUDGE_ATTEMPTS`) — after that a broken
|
|
722
722
|
judge is an instrument stop, never a red about the tree.
|
|
723
723
|
|
|
724
|
-
**The rulebook, and what each rule can raise** (`JUDGE_RULES` in `src/judged.js`; every rule carries a plain-words `means` the case-proposal prompt renders, so a proposed practice case never expects a red the rule cannot raise). The judge only POINTS (quotes lines verbatim); code does every check; unsure is red; the judge never says pass/fail. Quotes are matched by bareguard's `quoteIn(..., {wholeLines:true})` (bareguard
|
|
724
|
+
**The rulebook, and what each rule can raise** (`JUDGE_RULES` in `src/judged.js`; every rule carries a plain-words `means` the case-proposal prompt renders, so a proposed practice case never expects a red the rule cannot raise). The judge only POINTS (quotes lines verbatim); code does every check; unsure is red; the judge never says pass/fail. Quotes are matched by bareguard's `quoteIn(..., {wholeLines:true})` (`wholeLines` arrived in bareguard 0.21; bareloop now needs `^0.24.0`): a contiguous run of whole artifact lines, so a bare fragment cannot pass by hiding in a longer line, while a dropped leading `* ` of a JSDoc tag line still matches. `has-doc` checks only that a JSDoc block sits directly above the function (read off the artifact by `docBlockAbove`; the judge's quote is a pointer that must lie inside that block), so a documented function never breaks it. **`says-what`** (the doc's description must say more than the function's own name) reds when the described prose, minus a fixed list of function words and the name's own words (camel/snake/kebab, light inflections, and a prefix echo such as `init`/`initialize` when the shorter word is at least 4 letters), leaves nothing; no description, or an unlocatable quote, reds. It catches a pure name echo only ("Returns the total." for `getTotal` passes), and with NO doc block at all it stays silent and leaves the red to `has-doc` (the card is not required to carry both). `params` reds a declared parameter with no tag, an `@param` naming no parameter (each destructured slot absorbs one unmatched root tag), and, when the doc names any field of a simple destructured parameter, a missing field; `@param options` alone for `{ x, y }` passes. `returns` reds a returning function with no `@returns`. A practice case that expects a `has-doc` red on a function that has a block is refused as a legality red before any call (`validateCalibrationSet`).
|
|
725
725
|
|
|
726
726
|
**A judged stage may only judge files inside the job's write fence** (`judgedOutsideFence`, `src/authoring.js`, called from `validateDeclaration`, which every door reaches: the authoring revise loop, `validateCloseDecl` at signing and runner pre-flight, and `validateJob` for hand-written or reused specs). A path outside `writeScope` reds `judged-outside-write-scope` at draft time, because a finding on a file the run is fenced out of can never be fixed. With no fence to compare against (the plain-folder deferral) the rule is not run.
|
|
727
727
|
|
|
@@ -1437,9 +1437,13 @@ ambiguity, not a merge. `resolveHumanRuling(fresh, held)` is the exported seam t
|
|
|
1437
1437
|
`branch-red` is the WORK BRANCH refusing (below): the patient is not a git checkout, its
|
|
1438
1438
|
branch namespace has no free name, or a resume's recorded branch is gone. Zero tokens, and
|
|
1439
1439
|
never a fallback to working on the branch the run was handed. `provider-red` is a
|
|
1440
|
-
transport throw, or a scout
|
|
1440
|
+
transport throw, or a scout or first-draft round the API cut off mid-generation
|
|
1441
1441
|
(`truncated:max_tokens`, BA-6 — before which it laundered into a clean finish, F25): no
|
|
1442
|
-
verdict exists and the failed round's spend is only partly known (F6). A
|
|
1442
|
+
verdict exists and the failed round's spend is only partly known (F6). A REPLAN answer cut
|
|
1443
|
+
off at `max_tokens` is NOT provider-red (hamr 2026-10-16, F229): the half answer is never
|
|
1444
|
+
validated, the redraft is told in plain words to answer shorter, and a second cut ends as
|
|
1445
|
+
`plan-red` with an escalation `cause: 'truncated'` and the code-owned sentence "The replan
|
|
1446
|
+
answer was cut off at its length limit, so no new plan came back." A STEP or FIX-loop
|
|
1443
1447
|
worker round cut at `max_tokens` is NOT provider-red (F122 "2A" half b, hamr 2026-10-05, fix
|
|
1444
1448
|
phase added by ruling A): it is a failed attempt — the round is booked, its output discarded,
|
|
1445
1449
|
an `attempt-bounded` record carries the reason `output cut at the token limit (max_tokens)`,
|
|
@@ -3411,11 +3415,12 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3411
3415
|
- **`bareloop panel [--port N]`** (PANEL-BUILD.md P1) → a READ-ONLY `node:http` server
|
|
3412
3416
|
(no new dependency), bound to `127.0.0.1` ONLY, default port `4700`. Serves the panel's
|
|
3413
3417
|
look (Tokyo Night box-drawing TUI style) over the run list's real data: `GET /` the page;
|
|
3414
|
-
`GET /api/runs` (newest first, each row enriched with a glyph/check-type/spend/wall
|
|
3415
|
-
summary, `fileMissing:true` when the listed spine is gone
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
the
|
|
3418
|
+
`GET /api/runs` (P9 v2: `{runs, jobs, scheduled, gaveUp, limits}`; `runs` newest first, each row enriched with a glyph/check-type/spend/wall
|
|
3419
|
+
summary, `fileMissing:true` when the listed spine is gone, and the states the left list draws, all decided by the server:
|
|
3420
|
+
`live`, `liveWhy` (`step N of M`), `liveSince`/`priorWallMs`, `needs` (`'Resume'` while the engine would accept a resume and
|
|
3421
|
+
the run stopped under 7 days ago and was not removed; `'Accept'` while the run waits at its review door, `doorWaiting`), `stoppedAt`; `jobs` = `[{job, headRunid, quiet, activityMs}]`
|
|
3422
|
+
newest activity first; `scheduled` = the Scheduled section's cards and `gaveUp` = the Needs-you cards of schedules that gave up (both built by the schedule door, `listScheduled` / `listGaveUp`); `limits` = `{quietDays:30, olderRuns:7, needsYouDays:7}`. The page groups `runs` by `jobs` into Running now,
|
|
3423
|
+
Needs you, Jobs and a quiet fold; there is no Workflows / History switch and no separate `/api/workflows` endpoint); `GET /api/runs/:runid` (the full replay — parts/steps, counters,
|
|
3419
3424
|
summary, model/provider/judge). Every `GET /api/runs` row and the detail carry `runNo` (P6 item 7): runs of one job are numbered
|
|
3420
3425
|
oldest `at` first by ONE server function (`runNumbers`), and the page names a run `job (run-N)`; the run id stays in the Audit
|
|
3421
3426
|
tab and every command line. A run that came out of a panel drafting session also carries the DRAFTING part as the FIRST element of
|
|
@@ -3590,6 +3595,7 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3590
3595
|
Chat card in RESUME mode (title `RESUME <job> (run-N)`, filled from `GET /api/author/start-from`, every box locked
|
|
3591
3596
|
except the two caps, "spent so far" shown, Clear/Start drafting hidden) and `Sign & resume` on that card is the human
|
|
3592
3597
|
click, posting this same route; the run's Job tab is read-only on every run.
|
|
3598
|
+
**Remove from Needs you** → `POST /api/runs/:runid/needs-remove` (`src/panel/runroutes.js`, same `checkHumanGuard`, no body): appends ONE `{runid, type:'needs-removed', at}` row to `runs.jsonl` (`appendNeedsRemoved`; `readNeedsRemovals` reads it) and changes nothing else; 409 in plain words when the run is not in Needs you or waits for Accept. `readRunList` ignores the row type, so monthly accounting, the run counter and the scheduler never see it; a later stop (a newer `job-end`) puts the run back in Needs you. The run detail carries `canRemoveFromNeeds` and `needsAudit`.
|
|
3593
3599
|
**Stop (item 5)** → `POST /api/runs/:runid/stop` (`src/panel/runroutes.js`, `checkHumanGuard`, no body).
|
|
3594
3600
|
It writes the run's STOP REQUEST, a file `<spine>.stop` (`stopFilePath(spine)`, `src/legs.js` — the one
|
|
3595
3601
|
spelling), and signals nothing. `404` unknown run; `409` when the run's LATEST leg is not live
|
|
@@ -3613,34 +3619,38 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3613
3619
|
scout, plan or close-fix phase is honoured at that phase's next round boundary, the same as in a step (it used to be read in
|
|
3614
3620
|
step workers only). A stop in the close-fix loop reads "You stopped the run during the fix phase … a resume continues the
|
|
3615
3621
|
run" (`ralph`'s `phase` option; the step loop keeps "a resume re-enters this step").
|
|
3616
|
-
**
|
|
3617
|
-
|
|
3618
|
-
after `resume` when both show
|
|
3619
|
-
|
|
3620
|
-
code-owned origin line ("Copied from <job> (run-N) — <status word>. …").
|
|
3621
|
-
**Reuse workflow (replaces P5 item 3's Start from this, 2026-10-03)** — a button on runs that EARNED their green only
|
|
3622
|
-
(the Run tab's action row and the Ended block; `ended.actions` carries `{id:'reuse', label:'Reuse workflow'}` on `green`
|
|
3623
|
-
and green-with-destination-refused `green` rows, and on no other: an `already-green` run (the check passed before any
|
|
3624
|
-
work) offers Edit in chat instead, and a red row's next line is "Change the job: press Edit in chat."; stopped/capped/died
|
|
3625
|
-
rows offer `resume` and `edit`) and on every imported job; the Chat card's Check type is one radio group of three
|
|
3622
|
+
**Reuse workflow — ONE door on every run (2026-10-15, PRD v1.101; it replaced the green-only button and the old edit door)** —
|
|
3623
|
+
`ended.actions` carries `{id:'reuse', label:'Reuse workflow'}` on EVERY ending (green, failed, capped, stopped, died, every
|
|
3624
|
+
other red, already-green), after `resume` when both show, and on every imported job; a red row's next line is "Change the
|
|
3625
|
+
job: press Reuse workflow." The Chat card's Check type is one radio group of three
|
|
3626
3626
|
(Deterministic · Rubric · Reuse workflow), and the third opens a search picker over `GET /api/author/reuse-jobs` (below) that
|
|
3627
|
-
fills the card through this same path (a pick
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3627
|
+
fills the card through this same path (a pick puts the radio on the job's own check type). The button opens the Chat tab's New
|
|
3628
|
+
job card filled from the SIGNED job with EVERY box open (job lines and `~` rules, check type, judge examples, thinking, Inputs,
|
|
3629
|
+
Destination, Model, $ cap, Time cap) under a code-owned origin line "Reuse: <job> (run-N)"; nothing is locked and the server
|
|
3630
|
+
no longer refuses a changed job box. **The path is decided by the CARD, not the drafted spec** (`jobBoxesChanged(card, originCard)`
|
|
3631
|
+
in `src/panel/authorsession.js`): the JOB boxes are the job text, the check type and the thinking tick; the RUN boxes (Inputs,
|
|
3632
|
+
Destination, Model, $ cap, Time cap) never decide it. A GREEN origin (an imported job counts as green) with no job box changed is
|
|
3633
|
+
today's reuse — the signed plan verbatim, nothing drafted ($0), a new run of the same workflow signed under the ORIGIN job name.
|
|
3634
|
+
Any job box changed (green or red origin) drafts a NEW workflow named by the Job name box. A non-green origin with no job box
|
|
3635
|
+
changed also drafts (an unproven plan is never reused), signed under the ORIGIN name (another try of the same job). The name is
|
|
3636
|
+
fixed before drafting, so the hash the person signs already covers it. The Job name box shows the origin name until a job box
|
|
3637
|
+
changes, then the next free `<base>-N` (`GET start-from` returns it as `nextName`; renamable; base = the part before a trailing
|
|
3638
|
+
`-<digits>` when that part is itself a run-list or jobs/ job, so `x-2` suggests `x-3`); changing the boxes back restores the origin name. A name owned by a
|
|
3639
|
+
different job, or the origin's own name on a changed job, is refused at $0 ("A job named "x" already exists and is a different job.
|
|
3640
|
+
Pick another name."); a name typed while nothing is changed is ignored (the origin's is forced). The main button reads Sign & run on
|
|
3641
|
+
the reuse path and Start drafting otherwise, flipping as the person types. A new workflow has no schedule until its own first green;
|
|
3642
|
+
nothing here ever clears a schedule. Only the schedule card keeps its job boxes locked (it signs the green run's plan as it is).
|
|
3643
|
+
Routes, behind `checkHumanGuard`:
|
|
3633
3644
|
`GET /api/author/start-from?runid=<id>` or `?import=<id>` → `{ok, origin, card, from:'signed job'|'imported job',
|
|
3634
|
-
note,
|
|
3645
|
+
note, green, nextName, specHash, workflowKey, trackRecord:{runs,
|
|
3635
3646
|
green, notGreen, live, finished, avgSpendUsd, avgWallMs}, line}` (`404` unknown, `409` with an `error` when
|
|
3636
3647
|
there is nothing to copy: no log, the run's signed job is not on disk, or an imported job that changed, lost its
|
|
3637
3648
|
dependency or no longer matches its signed bytes); `POST /api/author/start` accepts `startFrom: <runid>` or
|
|
3638
|
-
`startFrom: {importId}` beside the card and answers `{ok, sessionId, reuse, state}
|
|
3639
|
-
|
|
3640
|
-
be reused — this one did not"` — the server owns the rule, not just the pickers. The old
|
|
3649
|
+
`startFrom: {importId}` beside the card and answers `{ok, sessionId, reuse, state}` (`reuse` false = drafting); the server
|
|
3650
|
+
makes the card-compare decision above, so a hand-made POST cannot reuse a plan that did not earn its green. The old
|
|
3641
3651
|
`POST /api/author/start-from-check` and the "Changed — new job" mode are gone. `GET /api/author/reuse-jobs[?model=<Name>]` →
|
|
3642
3652
|
`{ok, jobs:[{job, runid, checkType:'deterministic'|'rubric', line}]}` is the Chat card's Reuse workflow picker list: one entry per
|
|
3643
|
-
job (same `workflowKey`) with at least one
|
|
3653
|
+
job (same `workflowKey`) with at least one `green` or `already-green` local run (RULED 2026-10-15: an already-green run is a reuse source), `runid` = its newest such run (hand it to `start-from`), `line` =
|
|
3644
3654
|
start-from's own estimate line, newest green first; imported jobs are not listed. **Identity, two keys:** the
|
|
3645
3655
|
signature hash `jobSpecHash` is unchanged (it covers the caps and the write fence — what the person signs);
|
|
3646
3656
|
`workflowKey(spec)` (`src/job.js`, not exported from the package root) is a sha256 over the signed spec WITHOUT
|
|
@@ -3651,7 +3661,7 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3651
3661
|
when the Name is not the provider's default tier — spelled as authoring spells it; a RUBRIC origin with no `judge`
|
|
3652
3662
|
first gets `judge` = its own resolved judge identity so the judge never follows the worker; an explicit judge is
|
|
3653
3663
|
kept; deterministic jobs get none), and signs under a
|
|
3654
|
-
NEW `jobSpecHash` with the SAME `workflowKey` (the session refuses a spec whose key moved).
|
|
3664
|
+
NEW `jobSpecHash` with the SAME `workflowKey` (the session refuses a spec whose key moved). The job boxes carry the
|
|
3655
3665
|
signed values read by the Job tab's own readers. **Estimate line** above the card: "Same job — G green · N not
|
|
3656
3666
|
green · about $X and M min a run", from every LISTED run whose signed spec has the same `workflowKey` AND ran the same worker (provider + baseUrl + model) as the Model box now names — `GET /api/author/start-from?…&model=<Name>` recomputes it when the box changes; none = "Same job — no runs yet on this model" (one run =
|
|
3657
3667
|
one entry via `legsOf`; the averages read FINISHED runs with exact figures — spend from `chainSpend`, working time
|
|
@@ -3668,7 +3678,7 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3668
3678
|
**The progress list (2026-10-04)** — `GET /api/author/:id` state carries `steps: [{id, label, status:
|
|
3669
3679
|
'running'|'done'|'failed', detail}]`, one entry per pipeline step in first-seen order, updated in place (ids/labels:
|
|
3670
3680
|
`STEP_LABELS`, `src/panel/authorsession.js`); `progressLabel` is the running step's label. A drafting call after the first
|
|
3671
|
-
relabels the ONE drafting line `drafting (retry N of M)` (same line, never a second one). A refusal sets
|
|
3681
|
+
relabels the ONE drafting line `drafting (retry N of M)` after a refused try, `drafting (look N of M)` after a passed one (same line, never a second one). A refusal sets
|
|
3672
3682
|
`state.error` and fails exactly one step with the same text as its `detail`; pipeline text is never a chat message
|
|
3673
3683
|
(`messages` holds person/model turns only). Two last steps, `hash` ("generating hash", detail `spec hash <full hash>`, done at sign-prepare) and `signed` ("signed hash", pushed by `signRun` when the sign is accepted); the old "SIGNING PREPARED" / "signed — … run starting detached" thread bubbles are gone. The page draws each step's `detail` on its own `> ` line under the step. Destination follows the Source: for a repo source it is the write
|
|
3674
3684
|
fence (relative `writeScope` globs, never proven as a directory); for a folder source an absolute directory. Both
|
|
@@ -4319,7 +4329,7 @@ blast radius.
|
|
|
4319
4329
|
|
|
4320
4330
|
## Constraints
|
|
4321
4331
|
|
|
4322
|
-
- **Node >=
|
|
4332
|
+
- **Node >= 22** (`engines.node`; barebrowse, a production dependency since Part 1c, needs it).
|
|
4323
4333
|
- **Pure ESM + JSDoc**; generated `.d.ts` ship, never hand-written (LIBRARY_CONVENTIONS §2).
|
|
4324
4334
|
- **Secrets load from the environment** and never enter the spine, configs, or ledger —
|
|
4325
4335
|
an append-only record that captures a key captures it forever.
|
|
@@ -4330,7 +4340,60 @@ blast radius.
|
|
|
4330
4340
|
|
|
4331
4341
|
Goal, Success, Guardrails and Judge examples are one box, **The job**, and Source is **Inputs** (CLI interview and panel card alike).
|
|
4332
4342
|
|
|
4333
|
-
- **The job:** each plain line is numbered 1, 2, 3 (the number sits in a gutter outside the box). A `~` line has no number and belongs to the numbered line above; consecutive `~` lines join into ONE rule with `'; '`. A `~` line starting `PASS:` / `FAIL:` is a judge example (a rubric check needs at least one of each; a deterministic check refuses them); any other `~` line is a guardrail. A `~` before line 1 is a $0 red. Empty lines are ignored. `parseJobBlock(text)` → `{ ok, lines: [{ n, text, rule, examples }] }` or `{ ok:false, error }`; `jobBlockFitsCheck(lines, rubric)` is the Check-type cross-check.
|
|
4343
|
+
- **The job:** each plain line is numbered 1, 2, 3 (the number sits in a gutter outside the box). A `~` line has no number and belongs to the numbered line above; consecutive `~` lines join into ONE rule with `'; '`. A `~` line starting `PASS:` / `FAIL:` is a judge example (a rubric check that holds a judged part needs at least one of each; a rubric on a local file does not; a deterministic check refuses them); any other `~` line is a guardrail. A `~` before line 1 is a $0 red. Empty lines are ignored. `parseJobBlock(text)` → `{ ok, lines: [{ n, text, rule, examples }] }` or `{ ok:false, error }`; `jobBlockFitsCheck(lines, rubric, { judged? })` is the Check-type cross-check.
|
|
4334
4344
|
- **Inputs:** one `label: value` per line, numbered. Line 1 = the repo (absolute path). Lines 2+ = files or folders inside that repo, repo-relative; `proveInputFiles(inputs)` (`src/source.js`) proves them at $0 before any token — exists, inside the repo, tracked, and (files) not an environment file by name, not a symlink, text, under the size ceiling, a known secret shape in a file's content is MASKED wherever bareloop records it, never refused (the P6 worktree door ruling; `.env` by name and symlinks stay refused). A miss is `Inputs, line N: ...`. Search lines and files outside the repo are `only files inside the repo for now`. The panel proves them in `POST /api/author/start` (400 naming the line); the CLI re-asks.
|
|
4335
4345
|
- **Drafter:** every stage carries `fromLine`, or the line is listed in `closeDecl.refused` with a reason (see `closeDecl` above); the confirm turn's checks are `{ text, fromLine: [N, ...] }`; the plan draws a multi-line check under EACH line it serves.
|
|
4336
4346
|
- **Panel:** the card is `{ jobName, checkType, provider, model, thinking, jobText, inputs, destination, capUsd, maxWallMs }` (`provider` = the picked key's env name, `thinking` = the "Thinking on" tick). The confirm plan is a structured chat message `{ role: 'bot', kind: 'plan', plan }` and the Job tab reads the SIGNED spec through the same model (`src/panel/jobplan.js`: `planFromConfirm`, `planFromSpec`); the page draws both with one `renderJobPlan()`. `GET /api/runs/:id/job` returns `plan`, `jobLines`, `inputs`, `specHash`, `signedAt` (the run's FIRST `job-start`) and `signed` (`YYYY-MM-DD HH:MM · <first 6 of specHash>`, local time). An older spec (no `jobLines`) renders the signed goal as line 1 with its stage names as checks and the note `older job — numbered lines were not saved`; Reuse / Edit / Resume prefill the same fallback.
|
|
4347
|
+
|
|
4348
|
+
### A rubric job on your own local files — no judge yet (Part 1a, `src/rubricstep.js`, `docs/product/ITEM33-BUILD.md` "Addendum 2026-10-12")
|
|
4349
|
+
|
|
4350
|
+
A **Rubric** job whose first Input is the full path of a FILE (not a repo folder) works on your local text files; there is no repo, no language and no scout. Each Inputs line is `label: /absolute/file`.
|
|
4351
|
+
|
|
4352
|
+
- **Door.** `proveRubricInputs({ inputs, into, destination? })` (`src/source.js`) proves every line at $0 and freezes the files under `input/` of the same hidden-git tree a plain folder gets; `source.json` also records `inputs: [{ label, rel, sha256 }]` (hashed once, there). Refused in plain words naming the line and ending "Nothing spent.": a web address or a `search` line ("line N: web inputs (URLs, search) arrive in a later build. Nothing spent."), a relative path, a missing file, a folder, a symlink, an environment file, a file over 5 MiB (`RUBRIC_INPUT_MAX_BYTES`), text that is not valid UTF-8, a repeated label, a known secret shape. `isRubricFilesManifest(manifest)` tells this manifest from a repo's. Destination is the full path of a folder (the finished files are copied there on a minted green); a relative one is refused at $0.
|
|
4353
|
+
- **No examples needed.** `jobBlockFitsCheck(lines, rubric, { judged })`: the `~ PASS:` / `~ FAIL:` lines are required of a rubric job ONLY when it holds a judged part. A rubric on a file holds none in this build, so a job without examples is fine (written examples are kept and unused until the judge arrives). A rubric on a repo still needs them.
|
|
4354
|
+
- **One output per job line.** The worker writes `output/steps/<n>.json` for job line n: `{ "text": "...", "cites": { "<input label>": [ { "claim": "...", "quote": "..." } ] } }`. The worker's goal is written by code (`rubricGoal`) from the numbered lines and the frozen input paths, and states this contract (`stepFileContract`, one text shared with the composer's prompt). `output/steps/` is delivered with the rest of `output/` (`copyOut`: `steps/<n>-YYYY-MM-DD.json` under Destination).
|
|
4355
|
+
- **The close.** The text genre (`closeDecl.lang: "text"`, `RUBRIC_LANG`; `DECLARABLE_LANGUAGES` = the code languages plus `text`) has two kinds: the mandatory `changed-from-seed` guard and `rubric-step`, one stage per job line (`params: { step: n, parts: [...] }`, `fromLine: [n]`). A code job never sees `rubric-step` (`catalogueForLang`). `rubric-step` builds bareguard's rubric at every run (input shas from `source.json`) and grades the step file: `happened` (it exists and is a JSON object), `text` present, then the countable limits on `text` (`maxWords`, `minWords`, `maxLines`, `sections`, `sectionWords`) and `cited` per input the step cites (every quote found word for word in that input, spacing and `**` ignored; every number in a claim in its quote). A missing, cut-off or non-object file is a RED; an input that no longer matches its frozen sha, an unreadable `source.json`, a file over 5 MiB or a rubric that cannot be built is an INSTRUMENT STOP (exit 97). A step file cut inside valid JSON cannot be told from a short answer; that limit is stated, not hidden. A symlinked step file is red, never followed.
|
|
4356
|
+
- **Every part is sorted at drafting** (`class`: `countable` | `judged` | `guard` | `none`), and `checkRubricParts` reds in the person's words: `line N: this rule needs the judge, which arrives in the next build. Nothing spent.`; `line N: '<part>' can't be counted or checked against a quote. Say it as a count (words, sections, rows) or as something a quote must show.`; a guard (`line N: '<part>' is about web sources, and this job has no web input. Nothing spent.`); contradictions name both parts and both numbers; a stage must serve exactly its own line; a `cited` part must name one of the job's input labels. These codes are `rubric-*` and print whole in the drafting and signing stops (`draftStopText`, `signingStopText`).
|
|
4357
|
+
- **Signing is $0.** `prepareSigning` runs its paid calibration gate only for a close with a judged stage; a rubric-step close has none, so signing makes zero judge-round records. Not in this build: the judge, the final human accept, web inputs and search, chips (see the addendum's deferred list).
|
|
4358
|
+
- **Confirm turn (ruling 1A).** A rubric job on local files runs the same form -> confirm turn -> sign flow a repo job has (one confirm turn, two rounds at most; the scout stays off, the survey is the numbered inputs). `authorCloseForJob` runs it when `isRepo` or `lang === 'text'`. The protections shown are code's, never the model's: `confirmProtections({ verdictType, lang, writeScope, jobLines })` lists the text genre's guard (`changed-from-seed`), the write fence and, for every numbered line, `line N file — output/steps/N.json must exist, be complete JSON, and carry text`; the same list is fed to the model as ALREADY COVERED, and `RUBRIC_CONFIRM_NOTE` tells it a check here is a counted limit or a claim backed by a quote (anything needing judgement goes in `notChecked`). The confirmed plan reaches the composer, and its goal sentence rides in the worker goal as "The plan the person confirmed". `POST /api/author/start` skips the repo proof of Inputs for a rubric card whose first input is a local file (`looksLikeLocalFile`); the session proves each line.
|
|
4359
|
+
- **Fix-loop number.** A red `rubric-step` stage puts `detail.redCount` on its result: failing claims under a `cited` check (bareguard's `measured`) plus 1 per other failing check; `trendValueOf` hands it to the governor as `closeGrade` `{ stage, value }`, lower is better (no prose is parsed).
|
|
4360
|
+
- **Panel wording.** `stageQuestionText` words a rubric-step stage "line N — does its output pass the line's checks?". The worker brief also tells the worker to copy every number, time and price in a claim exactly as the input writes it ("9:27 AM", not "09:27").
|
|
4361
|
+
- **Shown == run (run mv5bk400).** The plan card after drafting is built from the signed close (`checksOfClose`, code-written sentences), not from the confirm turn's prose. A countable part may list the confirmed checks it carries out in `covers` (the composer prompt numbers them `#n`); `reconcileRubricPlan` moves every confirmed check no part covers to Not checked ("... — nothing in this close counts it or checks it against a quote"). `signRun` calls `planMatchesClose(session.state.finalPlan.checkItems, closeDecl)` and refuses, spawning nothing, when the two differ. `minClaims` / `maxClaims` (parts with `value` + `source`) count the claims listed under an input: `flattenStep` takes the count from the file's own `cites.<label>` list as the field `claims:<label>` and bareguard's `min`/`max` compares it. A free-text note is never a check.
|
|
4362
|
+
- **Labels.** A text close carries `genre: "TEXT"` (`genreForLang`; `TYPES` is for code languages, and a genre must match its language); its spine records carry no `judgedCount` (no judge ran); the progress list words a file job's steps from `FILE_JOB_STEP_LABELS` ("copying input files", "reading input files").
|
|
4363
|
+
- **Panel topbar.** `bareloop · v<version> · automate a job, verified`: the version is bareloop's own package.json, templated into the page at serve time (`__BARELOOP_PANEL_VERSION__`).
|
|
4364
|
+
- **After drafting, the code-worded card is the only plan on screen (run mv5e1ktb).** The confirm turn's plan bubble is the drafter's own prose (written before any close existed) and used to stay in the thread above the code-worded card; when the rubric card is ready the earlier `kind:'plan'` messages are dropped. `partSentence` now reads "1 line" / "1 word" for a count of one.
|
|
4365
|
+
- **Each `~` guardrail is its own row on the plan card.** `planFromSpec` / `planFromConfirm` lines carry `rules: string[]` (the line's own `rules`, else the joined `rule` split on "; "); `renderJobPlan` draws one `~ rule` row per rule under its numbered line, in the plan card and the Job tab alike (one renderer). Gutter numbers stay on job lines only.
|
|
4366
|
+
- **The review door's held line is true for a no-judge close.** Credit is held by VERDICT CLASS (`quarantinesCredit`: soft-green), whether or not a judge ran; the door now says "this is a rubric (soft-green) job, so its green earns NO reuse and NO learning credit until you accept it, whether or not a judge ran". Wording only; `bridges.js`/`reuse.js`/the Reuse refusal still say "rendered by the judged floor".
|
|
4367
|
+
- **A refused drafting try records why.** `authorClose` emits `onPhase('author-try-failed', { call, i, of, reds | reason, declaration? })` for a try refused by validation (the scrubbed reds), by a vacuous or stopped seed read (a `reason`), or by an all-cut-off reply; the panel writes `{kind:'try-refused', no, call, reason, reds?, declaration?}` to `draft-log.jsonl` (`tryRefusedText`, `src/authorreadout.js`, the code's own red details) and shows `<call>: refused — <reason>` on the drafting step's detail line. The CLI prints it via `phaseLine`. The refused declaration itself rides beside it (`declaration`, secrets masked; `declarationText` + `declarationCut` when over 60000 characters), so a failed draft can be read and replayed at $0 (`declarationRecord`).
|
|
4368
|
+
|
|
4369
|
+
### Required facts — a line that finds, lists or looks something up (Part 1b, `src/facts.js`, `docs/product/ITEM33-BUILD.md` "Addendum 2026-10-13" and "Part 1c", PRD item 33 RF1–RF22)
|
|
4370
|
+
|
|
4371
|
+
Check what the person asked for, not what the worker chose to write. bareloop needs bareguard `^0.24.0` (`cited` + `decides`, bind-only and `text` decides, word-edge anchors; 0.24 lets money currency/unit be lists, ruling #65, but bareloop facts still carry one form).
|
|
4372
|
+
|
|
4373
|
+
- **The part.** A `rubric-step` stage's `params.parts` may hold ONE part of `class: "fact"` (a line that finds, lists or looks up): `{ text, class: "fact", source: <input label>, facts: [{ name, shape, unit? | currency?, phrase?, anchor?, until?, rule?, aiProposal? }], count?: { min?, max? }, rules?: [n], covers?: [n] }`. `shape` is `money | number | date | time | phrase | text`; `rule` is `{ op: lt | lte | gt | gte, value }` or `{ op: "between", lo, hi }` (not on `text` or `phrase`); `money` names exactly one of `unit` (the input's own words, e.g. `"US dollars"`) or `currency` (`$ € £`); `phrase` carries the fixed `phrase` the quote must contain; `anchor` / `until` are labels copied VERBATIM from the input. A fact name is letters, spaces, `_` or `-` only (it is written into the claim text bareguard scans for numbers). A line with a fact part has no word, section or claim-count parts (a drafting red). Code fills `tested` (the early-test evidence) and `sample` (what the drafter saw of a big input) into the signed part; a model-written one is replaced.
|
|
4374
|
+
- **The step file.** `output/steps/<n>.json` of a facts line is `{ "results": [ { "quote": "...", "values": { "<fact name>": "<value as the quote writes it>" } } ] }` — no free text, no `cites`. ONE quote per result holds all its facts, so a worker cannot mix a price from one result with a time from another. `buildStepSpec` makes one bareguard `cited` check with `decides` (the fact's index is its `d<j>` key; a phrase fact is a presence decide and takes no value). The claim text is written by CODE from the values (`name: value; ...`); a `count` becomes `min` / `max` on the number of results. Every value is compared by shape and exactly (`8:15 AM` equals `08:15`, `389` equals `389.00`); a red names the result and the fact in plain words (`plainFactItem`). A JSON number in `values` is read as the string it prints as.
|
|
4375
|
+
- **No record cutting (Part 1c).** A fact part carries no `record`, `tested` or `sample` (a `record` field is an unknown-field red). The worker's brief says "one quote per result, copied from that result's own text" (bareguard's quote cap of 4000 characters stays its own). Drafting does not grade: it only proves each fact CAN be checked (below).
|
|
4376
|
+
- **Repeat rule (Part 1c M1, M4).** On a fact line asking for N results (`count.min`), `checkFactWords` (`src/factwords.js`, the one owner) refuses at $0 an anchor label the label list counts fewer than N times (`rubric-fact-label-rare`) and a `phrase` fact whose words `countPhrase` finds in the input fewer than N times (`rubric-fact-phrase-rare`); count 1 or none keeps a once-only one legal. `RUBRIC_RULES` states both.
|
|
4377
|
+
- **The look, then the draft (Part 1c).** For a rubric job on files, `authorCloseForJob` runs `runInputLook` (`src/authorscout.js`; pure parts in `src/inputlook.js`) on the frozen input text BEFORE the confirm turn and the draft, when a `provider` is present (`lookFn: null` = no look; a panel test that replaces `generate` gets none unless it passes `lookFn`). Read-only by construction: tools `search_input` / `read_input` over in-memory text, `LOOK_ROUNDS` 8, a reserved final round whose only tool is the strict answer form `report_look`, spend metered into the drafting book (`calls` absorbed like the repo scout's). Its report (facts with a label picked from the list, prose lines, not-found entries) is verified by code (`verifyLookReport`: a label off the list, an unknown shape/line/input, or a phrase not in the source is moved to not-found, named) and rendered into the drafter's prompt (`renderLookReport`); a failed or reportless look is an ABSENT survey and stops drafting at $0 (`scout-absent`, "the first look at your repo or input did not complete"). Every place, date or name in the job line is a fact too, bound to the label before it (a direction = phrase facts `Leaves <place>` / `arrives at <place>`); prose lines and their `~` limits are not looked up.
|
|
4378
|
+
- **Labels are picked, never typed (`src/labelscan.js`).** `scanLabels(text)` lists, per shape (money, number, date, time, text), the words right before each value with counts (top 40 per shape; one generic scan, no per-site table; a page reader's `[ref=N]` tags are ignored). `labelOnList(scan, shape, idOrText)` is the one test (id or exact words, white-space blind). The look and the drafter pick labels only from the list shown in the prompt (`renderLabelList`). Fact-line reds are plain sentences; the confirm step and the drafter share one rule text (`RUBRIC_RULES`), and the drafter's per-shape field guide is generated from bareguard's `rubricVocabulary`.
|
|
4379
|
+
- **Found-once check (`src/factwords.js`, `checkDeclarationWords`, run after validation in `authorClose`).** Every anchor must be on the input's label list for its shape, and every `until`, phrase and unit must be in the source once (white-space blind: U+202F, U+00A0; phrases whole-word, case-blind). A miss is a red naming the word and the input: `rubric-fact-label-off-list`, `rubric-fact-word-missing`, `rubric-fact-unread` (an input that cannot be read is a red, never a skipped check). Nothing is graded; finding and grading are the run's and the close's job.
|
|
4380
|
+
- **No silently dropped rule (RF16).** When a close holds a fact part or any part with `rules`, every `~` rule of every job line must be carried out by a part (its number in the part's `rules`, 1 = the first `~` rule) or listed in `refused` as `{ line, rule, reason }`, or its whole line refused; a rule that is none is a $0 red (`rubric-rule-dropped`). `refused` entries may now carry `rule`. A number the person did not write (a bound not found in the line's own words) must be marked `aiProposal: true` (the card says "AI's proposal — check it"), and an AI-proposed time limit must be `between`, never one-sided (RF17).
|
|
4381
|
+
- **The prose step.** A line that writes keeps its word / section / line counts. When any fact line comes BEFORE it, code puts one `numbersFromFacts` part on it (`withFactBindings`; the model never writes it, and a missing or wrong one is a drafting red): every number in `text` must appear in a quote of those fact lines' step files (bareguard's `numbersInQuote`: token match, not canonical, so `v0.41.0` is one token). The card says, under Not checked, `line N: what the text says — you judge it at Accept`. At the close, a tied earlier line whose step file does not exist yet (the seed read, before any worker wrote) is a normal red ("line N's answer is not there yet"), never an instrument stop; only an unreadable file is a stop.
|
|
4382
|
+
- **The plan card.** One code-worded row per fact under its line, read by the one reader (`checksOfClose` / `factRows`) the confirm card, the Job tab (`planFromSpec`) and the sign-time `planMatchesClose` share, e.g. `fact · price — money in US dollars, at most 450 US dollars, after "Price", quoted from "Flights"`, then the count row. A `~` rule the drafter listed as not checked reads `line N: "<rule as written>" — <reason>`.
|
|
4383
|
+
- **The worker brief.** `rubricGoal({ ..., closeDecl })` adds a code-written brief per line (`lineBriefs`): a facts line gets its facts and its count; a text line that follows gets "every number must be copied exactly as written in a quote you wrote for job line N". `stepFileContract()` names both shapes.
|
|
4384
|
+
- **Web addresses as inputs (Part 1c, `src/pagefetch.js`, `src/source.js`).** An Inputs line may be `label: https://...` (also a rubric card's FIRST line; `looksLikeRubricInput`). `fetchPageText(url)` calls barebrowse `connect({ incognito: true, blockPrivateNetwork: true })` (`CONNECT_OPTIONS`) → `goto` → `waitForNetworkIdle({ idle: 1500, timeout: 20000 })` (bounded; a page that never settles is read as it stands) → `snapshot({ mode: 'act' })`, always closing the browser, with our own 90 s deadline (L1: a load-time `browse()` snapshot gave 77,951, 8,801 or 719 characters of the same results page; L2: a page whose text outside the banner / navigation / contentinfo landmarks is under `MIN_CONTENT_CHARS` = 100 (`pageContentChars`) is a stop too, "only the site's own header and menus", and an input with no content never reaches the look or an early ask: the survey is ABSENT, "holds nothing to read", nothing spent); an empty or under-200-character page (`MIN_PAGE_CHARS`), a bot challenge, an unreachable address, a credentialed or secret-shaped address, or a page carrying a secret shape is a plain-words $0 stop on its line. `proveRubricInputs({ ..., fetchUrl })` freezes the page text under `input/<label>.txt` and records `url` on the manifest input row. A FRESH `run-u --spec` start (not `--resume` / `--door` / `--decide`) calls `refreezeUrlInputs`: it fetches every recorded address again, writes that text into the prepared tree, commits it as the new seed and rewrites `source.json` (new `sha256` and `seed`, `draftSha256` keeps what the look saw) BEFORE the worker starts, so quotes are checked against the run's copy and the worker reads it, never the live page. A failed or empty fetch stops the run, "The run did not start. Nothing spent." Plan card (L3): `settlePersonsWords` (called in `authorClose` before validation) unmarks AI's proposal on a limit made of the person's own numbers, swaps in the one explicit positive time range the person wrote, and drops a `refused` entry for a `~` rule a part of its line carries; the look's fact may carry `forRule` (a rule it numbered from its own knowledge, marked AI's proposal), which removes that rule from the look's not-found list so it is not asked. Test seams: `fetchPageText(url, { connect })`, `deps.fetchUrl` (`run-u`), `deps.fetchUrl` / `deps.lookFn` (`createSession`).
|
|
4385
|
+
- **Ask at once, only about intent (I1).** A look not-found entry that names a `~` rule becomes a question worded by code (`ruleQuestions`: line and rule named), asked before the confirm turn through the existing question gate (`answerQuestions`; `normalizeQuestions` caps it at `MAX_QUESTIONS`); the answer is a new `~` rule on that line. A rubric job on files never offers the piecemeal try-3 question; its failed-draft stop (`draftStopText({ wholeJob: true })`) says to review the whole job and submit it again.
|
|
4386
|
+
- **Known limits (documented, not built around).** Airport codes like SFO have no stable label on a flight page: direction is checkable only through the full airport names the page uses. An unanchored `text` fact cannot tell which field a value sits in. `am` / `pm` in lower case and `9:27AM` are malformed times; `v0.41.0` is one token. A fact step's quote proves the value is in a real record, not that it is the RIGHT record: "the cheapest" and "the last 3" are Not checked. Verified on real files only; the live proof (DeepSeek, both jobs through the panel) has not been run.
|
|
4387
|
+
|
|
4388
|
+
## Triggers — a job that starts by itself (panel-only; `src/trigger*.js`, `src/panel/schedules.js`, `schedulestart.js`, `scheduler.js`, `src/schedulewords.js`)
|
|
4389
|
+
|
|
4390
|
+
- **The `trigger` field of a signed spec** is a typed object, never cron text: `{kind:'daily'|'weekdays', hour, minute}`, `{kind:'weekly', day:'mon'..'sun', hour, minute}`, `{kind:'new-file', folder}` or `{kind:'file-updated', path}` (absolute paths). By hand = the key is ABSENT, so a by-hand spec hashes as before. `workflowKey` ignores it. The agent never sets it: `assembleSpec` refuses a draft that carries it.
|
|
4391
|
+
- **Only the panel fires.** `createPanelServer` runs one 500 ms unref'd scheduler (poll only, never `fs.watch`); the CLI never fires. Panel off = nothing fires; slots missed collapse to ONE catch-up (marked when more than 2 minutes late). `createPanelServer` test seams: `scheduleNow`, `schedulerIntervalMs` (0 = no timer), `sessionDeps`, `acceptWaitMs`.
|
|
4392
|
+
- **Files live under `<config home>/triggers/<job>/`**: `schedule.json` (the current signed schedule) and ONE append-only `ledger.jsonl` (every row carries `sig`, the spec hash it was written under). A run that a schedule started carries `trigger.json` beside its `resolved-spec.json`.
|
|
4393
|
+
- **Setting one is two clicks, both $0** (`POST /api/author/schedule-prepare`, then `/api/author/schedule` with the hash). It needs a real green run of the job's CURRENT workflow (a rubric job also an honoured Accept). A file trigger is for plain-files jobs only. Cancel is never refused. A reuse never clears a schedule: Reuse workflow (the one door, v1.101) keeps the origin's name while no job box is changed and switches the Job name to the next free `<base>-N` once one is; a changed job is a new workflow that needs its own green before it can be scheduled.
|
|
4394
|
+
- **A fire goes busy, then monthly limit, then `wouldAccept`, then the one starter** (the same session start and `signRun` as a Reuse the person clicked, with `--review-door`). Busy, monthly and refused fires consume nothing and are told once per `key|outcome|why`, so they retry. A run is busy while its pid is alive AND its process started no later than 5 s after its run row was written (a recycled pid is not that run). A run waiting at its review door is not busy.
|
|
4395
|
+
- **An install-needed refusal backs off, then gives up.** When a fire's copy needs packages, that job's next attempt waits 1, 2, 3, 5, 8, 13, then 21 minutes (in memory: a restart begins at the first wait); a wait that would pass 60 minutes in total is not taken: the schedule writes ONE `gave-up` row and is paused (nothing fires; files still queue as `arrived`), and the job shows in Needs you with the pill `Check` (`listGaveUp`). Signing the schedule again writes `resumed` and clears it. Constants: `INSTALL_RETRY_STEPS_MS`, `INSTALL_GIVE_UP_MS` (`src/panel/schedules.js`).
|
|
4396
|
+
- **A scheduled green is held until accepted**: `getStartFrom` reads its outcome as `green-held` and the Reuse list skips it, until the spine holds an honoured accept.
|
|
4397
|
+
- **Accept** (`POST /api/runs/<id>/accept`) runs the engine's own door path (`run-u --spec <signed spec> --door <id> --decide accept --approve <hash>`): the mechanical stages re-run, a red is `door-accept-red`, the run's verdict is untouched. It shows only while `doorWaiting(events)` (`src/reviewdoor.js`) is true; the same answer fills the runs list's Needs-you `Accept`.
|
|
4398
|
+
- **Every word the page shows** comes from `src/schedulewords.js` (`SCHEDULE_WORDS` is the one button table the server sends).
|
|
4399
|
+
- **The schedule is shown on three tabs** (v1.102): the Run tab's one summary block (ENDED dated, NEXT, SCHEDULE with `last`/`next`; row and frame red when the last fire started nothing), the Job tab's Started and Schedule rows (`getRunJob`), and the Audit's own folded `schedule` section on every run of a scheduled job (v1.104: `detail.scheduleSection` `{title, sign, head, rows}`; a run a cancelled schedule started keeps `detail.startedAudit`). `/api/runs` `scheduled[]` and `gaveUp[]` carry `runNo` so the small lists read `job · run-N`. `trigger.json` carries `triggerKind`, `sessionId`, `originRunid`, `sig`, `lateMs`; each signing appends a `signed` ledger row naming its origin run.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "Workflows that earn their own design, with receipts — agent-authored workflows for repeated, long, verifiable jobs that improve across runs under an un-gameable gate.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"author": "hamr0",
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": ">=
|
|
20
|
+
"node": ">=22"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "node scripts/hermetic.mjs && node scripts/prompt-commit-check.mjs --range origin/main..HEAD",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"bare-agent": "^0.52.0",
|
|
31
|
-
"
|
|
31
|
+
"barebrowse": "^0.21.0",
|
|
32
|
+
"bareguard": "^0.24.0",
|
|
32
33
|
"litectx": "^0.32.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|