One binary: the lint and the trainer are commands of portal
Test / test (pull_request) Successful in 1m7s
Test / test (pull_request) Successful in 1m7s
portal serve runs a site; portal lint is what iris was; portal plan, build, report and the rest are what portal-trainer was. One source at one version, so the three never disagree about what content means. Both histories are merged in (subtree). The release attaches the bare binary for a content repo's CI. The trainer's plans, intakes, engines and reference prompt come along; its runs stay in the old repo. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
d0d1cbf23d
commit
96b3746643
+151
@@ -0,0 +1,151 @@
|
||||
---
|
||||
name: portal-trainer
|
||||
description: Onboard a business onto portal - interview them, write a plan, build the content repo (pages, aggregates.yaml, desks, mails) with the words written by a model, and score it until iris proves every need is met and a blind visitor routes the personas right. Use when someone wants a new portal site, a new flow on an existing one, a rebuild of a live one, or asks why visitors pick the wrong alternative.
|
||||
---
|
||||
|
||||
# Portal trainer
|
||||
|
||||
You are onboarding a business onto [portal](https://project.uhhm.no/uhhm/portal):
|
||||
a site that is one question per page, a few alternatives per question,
|
||||
each alternative optionally a form whose answer is recorded into a
|
||||
bucket with a state machine, worked from a group-gated desk, with a
|
||||
mail to the person when their case moves.
|
||||
|
||||
You have a verifier. Use it instead of your judgment wherever it can
|
||||
speak: `iris check --path <repo>/questions` (uhhm/iris, the release
|
||||
matching the portal the site runs) exits non-zero until the structure
|
||||
meets every need in `<repo>/needs.yaml`.
|
||||
|
||||
## The order of work
|
||||
|
||||
1. **Interview, then write the plan.** Never pages first. `plan`
|
||||
reads a filled-in intake into `plan.yaml` (a model decides needs,
|
||||
stages, forms and budgets; a person reads the result before
|
||||
anything is built). By hand, the same questions: who comes to the
|
||||
business and what each of them wants, who inside handles it, what
|
||||
"handled" means (the states a case ends in), what it costs the
|
||||
visitor to ask (`max_fields`, from the person, never from counting
|
||||
the form you would like). Start from a `sectors/` file when there
|
||||
is one.
|
||||
2. **Say what the plan can say.** `does:` (one or two features, one
|
||||
role each, the front door's opener that asks for nothing);
|
||||
`shows:` (things to look at before asking); `doors:` only when the
|
||||
offers are more than a page holds - measure it, a flat page with
|
||||
ten offers scored worse than four doors; `mails:` per need (who
|
||||
hears when a case enters a stage) with `mail: { from }` on the
|
||||
plan; `moves_by: submitter` so the person has something to play
|
||||
after sending; `invites_by` for onboarding; `events:` when
|
||||
rebuilding a live site, so its records keep replaying.
|
||||
3. **Write personas in the visitor's words, not the business's.** Two
|
||||
or three per need, one who would not use the trade's vocabulary.
|
||||
Never reuse a phrase you intend to put on a page. They are held
|
||||
out from every model that writes words.
|
||||
4. **Build.** `build --plan ... --lint iris` derives every file with
|
||||
no model deciding structure; `--llm` lets a model write the words,
|
||||
`--llm-for <path prefix>` only some of them (the mails alone);
|
||||
`--words-from <live repo>` keeps a live site's hand-edited words
|
||||
through a rebuild, following each card across pages; `--words
|
||||
<json>` overrides slots. The builder refuses a reply in the wrong
|
||||
language and puts back a submitter's link a model dropped.
|
||||
5. **Score.** `iris check --needs-tasks` then `answer`, then
|
||||
`--needs-score`, with and without descriptions; `rate --repo` for
|
||||
readability and the four understanding questions, mails included.
|
||||
A card with no button is context, never a choice. Trust the
|
||||
descriptions score; the headings-only score swings by a third
|
||||
between seeds. Before believing a change, `runs/spread.sh` it over
|
||||
three seeds and require it to beat the spread. A change that does
|
||||
not is rejected and kept as a run.
|
||||
- A **misroute** is a wording defect until the same one survives
|
||||
every rewording; then it is a seam in the plan.
|
||||
- Change the page or the plan, never the persona.
|
||||
6. **Keep the run.** `runs/<business>-<date>/` with the words as used
|
||||
(`words.json`), tasks, answers, scores per seed, and a README with
|
||||
what moved and why. Runs are the record of what worked.
|
||||
|
||||
## What a page is
|
||||
|
||||
Full reference: portal's `README.md`; a live example is
|
||||
`uhhm/questions`. The tree under `questions/` is the router
|
||||
(`index.yaml` is `/`, `_section.yaml` applies `qualifies` to a
|
||||
directory, nested non-index files are followups).
|
||||
|
||||
```yaml
|
||||
# questions/index.yaml
|
||||
name: What brings you here? # the question - always a question
|
||||
responsible: { name: ..., contact: ... }
|
||||
alternatives:
|
||||
- name: What we do # the opener: features, no button
|
||||
features:
|
||||
- { name: <a role's win>, description: <what we do for them> }
|
||||
- name: <heading the visitor picks> # graded alone under --skim
|
||||
description: <one or two sentences>
|
||||
action: /received # where the submission lands them
|
||||
consequence: [Send] # the button
|
||||
record_as: leads # bucket - must exist in aggregates.yaml
|
||||
features:
|
||||
- name: <form section>
|
||||
requirements:
|
||||
- { name: email, label: Email, type: email }
|
||||
- { name: problem, label: What's wrong, type: textarea }
|
||||
```
|
||||
|
||||
```yaml
|
||||
# aggregates.yaml
|
||||
aggregates:
|
||||
- bucket: leads
|
||||
initial: open
|
||||
states:
|
||||
open: { event: received, mail: { to: submitter, subject: "We have it", body: "..." } }
|
||||
quoted: { event: quoted }
|
||||
won: { event: won, mail: { to: submitter, subject: "...", body: "..." } }
|
||||
lost: { event: lost }
|
||||
transitions:
|
||||
open: [quoted, lost]
|
||||
quoted: [won, lost]
|
||||
```
|
||||
|
||||
```yaml
|
||||
# site.yaml
|
||||
mail: { from: "Nordvik <post@nordvik.example>", reply_to: ola@nordvik.example }
|
||||
```
|
||||
|
||||
```yaml
|
||||
# questions/desk/_section.yaml
|
||||
qualifies: project_managers
|
||||
# questions/desk/index.yaml
|
||||
name: What came in?
|
||||
alternatives:
|
||||
- name: Leads
|
||||
action: /desk
|
||||
consequence: [Confirm]
|
||||
features:
|
||||
- name: ""
|
||||
resource:
|
||||
source: { kind: kv, bucket: leads }
|
||||
requires_group: project_managers
|
||||
transitions:
|
||||
- { to: quoted, label: Quote sent } # from defaults to open
|
||||
- { from: quoted, to: won, label: Won }
|
||||
```
|
||||
|
||||
## House rules for the copy
|
||||
|
||||
- The page name is a question the visitor could ask themselves.
|
||||
- The front door opens modestly: what the business does for one or
|
||||
two roles, then what there is to look at, then the offers. No
|
||||
second tier.
|
||||
- Headings say what the visitor gets, in the visitor's words. Lead
|
||||
with what a thing enables; never define it by what it isn't.
|
||||
- Every required field must be one the business will use to answer.
|
||||
- A followup page says who reads it, when they answer, and what the
|
||||
visitor can do meanwhile. A mail says what happened and, where the
|
||||
person has a move, the link to make it.
|
||||
- Code in English; the site in the business's language.
|
||||
|
||||
## Engines
|
||||
|
||||
Any engine that reads a task's `state` (`body` is the persona,
|
||||
`page` what the page says before its options) and `questions` and
|
||||
returns `{"id","choice","confidence"}` per line works. The visitor
|
||||
and the writer must be different models. See the repo README for what
|
||||
has been measured about each, and for the noise floor.
|
||||
Reference in New Issue
Block a user