A stray placeholder or a lost link was only fixed when a model wrote
the mail, so a words file that already carried one kept it through
every later build. The pass now runs once on the finished
aggregates.yaml words, covering --words, --words-from and the model
alike; the football club's three mails lose the `{chain}` they were
shipped with.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The mail prompt said "short"; it now says how short. The football
club's enrolment mails halved in length and read easier, and the
vel's evened out, with every link kept.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The second Norwegian case: level with its earlier build on routing
inside the spread, desks 13 of 14 with outcomes in words, Norwegian
mails from Sonnet in one pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Live vel front door against a cold, bureaucratic twin: friendlier 9
of 10 to the live page, none to the twin, both orders. So the earlier
no-preference between the live doors and the rejected rewrite was a
verdict, not blindness; and judged clarity and measured routing are
different things.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Re-planning the contractor showed three wasted rounds: `moves_by: {
checking: submitter }` written as a bare word, a group without a
label, and the two featured owner roles left with nothing to play,
which the builder only warned about after the fact. The bare word and
the missing label now parse (a label defaults to the id), and a plan
that parses with spectator roles is sent back once with the finding;
on the contractor, round three fixed both.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A need may say which unfinished stages a case can still be turned
down from; the machine no longer offers the bad end from every stage.
uhhm's collaborators are declined before they start and depart after,
as the live site has it. The uhhm plan gets stages_text and three
desk buttons reworded: every uhhm desk scores full marks.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The first desk score offered every button of the bucket, where portal
shows a row only the moves from its own state; that scored the live
vel 17 of 26 and sent a rewrite of the neighbour-help desk chasing an
instrument fault. With the row's buttons only: 24 of 26, and the
rewrite loses on the fair test too. Noted: an accent-stripped state id
(ikke_lost) reads as English even to a strong picker.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A model asked for Norwegian sometimes answers in English. The commonest
function words of each language are counted across a reply, and one
that reads as the other language is refused with a line saying so.
Tomter Vel's six mails, worded by Sonnet in Norwegian, are round 2 of
runs/tomtervel-opener-2026-09-23 and live.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sonnet wrote the ten uhhm mails and replaced the decide link with a
bare {chain}. keep_links puts the fallback's link line back into any
mail that lost it. The mails read easier than the fallbacks (LIX 9 to
33 against 18 to 28) and say something.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every score so far was one run of a small visitor. `answer --seed n`
answers the same tasks with other shuffles and sampling, and
runs/spread.sh prints a run's score per seed, so a change can be told
from the visitor's mood.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`does:` on a plan, at most two features, each `for:` a need: the first
card of the front door says what the business does, one role each,
headed by what that role gets, and asks for nothing. `shows:` is the
second card, portal resource blocks passed through verbatim plus every
public view. Only then the offers, as more alternatives on the same
page; the front-door warning moves to seven and stops recommending
doors. `Plan::spectators()` warns when a featured need gives the
submitter neither a move nor a view. uhhm.no rounds 4 and 5 (flat,
at least as good per persona) and Tomter Vel flat (worse: ten offers
on one page, the close pairs collide) are in runs/.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
Sonnet writes, qwen3:8b routes: 16 then 19 of 27 with descriptions,
19 then 21 of 27 on headings alone. One card took seven personas
until it named redoal; what remains is on the front door and mostly
a judgment the plan could make differently, not the words.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`invites_by` on a plan (and `invited_by` on a group) put an invite form
per fillable group on the inviting group's desk, plus a list of the
invites sent - portal's `invite:` does the onboarding. Every desk
group invites into itself by default; an inviter without a desk is
refused.
`--words-from <repo>` lifts the words an existing repo carries back
into the words map by structure: the plan is rendered with a token in
every slot and walked alongside the repo's files. Hand edits and
model-chosen icons survive a rebuild; only new slots get fallbacks,
which `--words <json>` can override. The words as used are written to
words.json. First use: Tomter Vel, additions only.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
Work from another session, committed as found (tests green):
- `plan` reads an intake into a plan.yaml (a model decides needs,
stages, forms and budgets; a person reads the result before build).
- Plans gain `doors` (a first question before the forms, when a front
door would carry more than five needs), `groups` with labels that a
select may draw its options from (`options_from: groups`), `private`
fields kept off public pages, `multiple` selects, and `max_fields`
as what the intake said the person will tolerate.
- `card.rs`: an alternative as the visitor meets it, in text, so the
answering visitor and `rate` read the served card, not the heading.
- `rate` reads followup pages from the repo and reports "what happens
next" as not measured rather than zero when it cannot.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
plan.yaml: `moves_by` says who presses the button into a stage when it
is not `handled_by` - other groups, several groups, or `submitter`,
the person who sent the form, through their own link (the form must
then collect a required email). `watched_by` puts a bucket on a
group's desk with no buttons. Each group's desk carries its own buttons
only; a submitter's moves become a followup page of self_transitions;
needs.yaml gains `also_moved_by` for portal's verifier.
simulate: a route is now a relay across every role that holds a legal
button, the submitter included, and each step is judged as the role
that presses it. `--dry` asks no model anything and walks every simple
route of every aggregate, for needs_replay to put through the real
engine.
plans/contractor-full.yaml: 13 needs, 6 roles, 10 aggregates. Verifier
green by construction; 40 routes, 78 transitions accepted by the real
engine, none refused. What it exposed is in its run README.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
Fallback words read like finished copy, and nemotron handed the front
door straight back. The prompt now shows every string as a slot saying
what belongs there; a slot echoed back counts as no answer and keeps
the fallback.
runs/contractor-build-2026-09-21: nemotron-3.5-lightning:30b-a3b, the
model that produced no loadable site free-form, words a complete
contractor site in 130 s. Verifier green by construction; held-out
personas route 8/8 with descriptions and 8/8 on headings alone
(qwen3:8b as the visitor). "What happens next" is still 0/2 on the
front door: no heading or description says who reads a submission.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
Every prompt and reply is now kept under <out>/llm/. An empty reply is
a retry, not a fatal error; a spec that never parses stops the run
before the structure phase; --visitor-llm lets a different model play
the visitor. The file-block parser finds markers anywhere in a line,
accepts a short END and lets a new FILE close an open one, after a
reply glued two files together with `<<<END>></think>`. The reference
gains a language section: copy in the intake's language, identifiers
in ASCII.
runs/rehab-entreprenor-no-2026-09-21: nemotron-3.5-lightning:30b-a3b
did not produce a loadable site. Written up with the reason and the
way forward: structure from needs.yaml deterministically, words from
the model.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
Readability is counted (LIX), not judged. Understanding is three
yes-or-no questions per persona, with a gutted twin of every page as
the control: an instrument that still says yes to "Option 1, Option 2"
is not measuring the page. Friendliness is a preference between two
versions shown in both orders; a pick that flips with the order counts
as no preference. Page load (options offered, required fields in the
step) flags a page that should be split into a further question or
have fields moved to a later stage.
First run on the generated contractor site, qwen3:8b as the visitor:
control passed on every need; "what happens next" was 0/2 everywhere
and 2/2 everywhere once the page said who reads it and when (LIX 28 to
30); an official-voice control reads LIX 63 against 27. Friendliness
mostly came back "no preference" at two personas per need: directionally
right (never preferred the cold version), too thin to rely on.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
runs/contractor-2026-09-21: the generator's first end-to-end run, on
intakes/contractor.yaml. Needs parsed first round, structure green
first round, 10/10 on its own personas - and 8/10 headings-only on
ten held-out personas with qwen3:8b as the visitor, because the pages
had been written with the personas in view. Phase 2 now shows the
model the needs without the personas.
`portal-trainer answer` exposes the blind answerer on its own. README
rewritten around what this is: an intake in, a verified content repo
out.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
`portal-trainer generate` turns a plain-words business intake into a
content repo in three phases, each held to portal's question_lint:
needs.yaml (then frozen, so the generator cannot weaken the spec to
pass), structure (revised against the lint's findings until green),
and wording (personas answered blind, misroutes fed back; a rewrite
that breaks structure is reverted). Model output is written to disk
only under aggregates.yaml, site.yaml and questions/**.yaml.
`portal-trainer answer` is the blind answerer alone.
The LLM is an external command, prompt on stdin, text on stdout:
engines/ollama.sh (local Ollama), engines/anthropic.sh (API key),
engines/mailbox.sh (hand each prompt to an agent or a person).
portal-portal/ is the onboarding site as a portal content repo: the
front door is the intake interview, records land in `onboardings`
(open -> drafted <-> revising -> live | declined), and it passes its
own needs spec.
runs/uhhm-2026-09-21/ollama: qwen3:8b and granite4.1:8b as blind
answerers on the same eight tasks.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
The trainer procedure as an agent skill, the engine contract, two
sector specs (software-studio: UHHM's own, green; construction: a
target with no pages yet), and the first wording run against uhhm.no.
The verifier itself is portal's question_lint (uhhm/portal 188f9bf).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH