portal-trainer: the loop around portal's needs verifier
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
This commit is contained in:
co-authored by
Claude Fable 5.1
commit
7caaa27ba7
+136
@@ -0,0 +1,136 @@
|
||||
---
|
||||
name: portal-trainer
|
||||
description: Onboard a business onto portal - interview them, write their needs.yaml, then write the content repo (pages, aggregates.yaml, desks) until question_lint proves every need is met and an answering engine routes the personas right. Use when someone wants a new portal site, a new flow on an existing 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.
|
||||
|
||||
You have a verifier. Use it instead of your judgment wherever it can
|
||||
speak: `question_lint --path <repo>/questions` exits non-zero until
|
||||
the structure meets every need in `<repo>/needs.yaml`.
|
||||
|
||||
## The order of work
|
||||
|
||||
1. **Interview, then write `needs.yaml` first.** Never pages first.
|
||||
Ask who comes to the business and what each of them wants, who in
|
||||
the business handles it, and what "handled" means - the states a
|
||||
case ends in. Ask what it costs the visitor to ask: how many
|
||||
submissions, how many required fields they will tolerate. If the
|
||||
business is in a sector under `sectors/`, start from that file and
|
||||
cut it down with them; do not start blank.
|
||||
2. **Write personas in the visitor's words, not the business's.** Two
|
||||
or three per need, including one who would not use the trade's own
|
||||
vocabulary. Never reuse a phrase you intend to put on a page - a
|
||||
persona that quotes the page tests nothing.
|
||||
3. **Run the lint. It fails. Read every line.** Each failure names
|
||||
what is missing: no path, a closed (`disabled`) path, a field budget
|
||||
blown, no desk held by the handling group, a finished state no desk
|
||||
move reaches, a state records strand in.
|
||||
4. **Write the least content that turns it green**: `aggregates.yaml`
|
||||
(one aggregate per bucket, states the business named, only the
|
||||
transitions they described), the visitor pages, one followup page
|
||||
per recording alternative saying what happens next, and one desk
|
||||
page per handling group with exactly the moves the state machine
|
||||
allows. Mark a need `planned: true` only when the business says
|
||||
"later" - it keeps warning.
|
||||
5. **Then word it.** `question_lint ... --needs-tasks tasks.jsonl`,
|
||||
and again with `--skim` (headings only - most visitors read nothing
|
||||
else). Give the tasks to an answering engine that never sees
|
||||
`expect`; grade with `--needs-score answers.jsonl`.
|
||||
- A **misroute** is a wording defect. Fix the heading before the
|
||||
description: a heading that needs its description has failed.
|
||||
- Two alternatives a persona confuses are usually one distinction
|
||||
the business makes and the visitor does not. Name the visitor's
|
||||
side of it.
|
||||
- **Hesitant** (right, under 0.6) means it works and is still a coin
|
||||
flip. Same treatment.
|
||||
- Change the page, never the persona, to make a score go up.
|
||||
6. **Keep the run.** Copy tasks, answers and the score into
|
||||
`runs/<business>-<date>/` with a line on which engine answered.
|
||||
Runs are the record of which wording worked, per sector.
|
||||
|
||||
## What a page is
|
||||
|
||||
Full reference: portal's `README.md` and
|
||||
`docs/design/filesystem-routes.md`; a live example is
|
||||
`uhhm/questions`. The file 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: <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 }
|
||||
- { name: address, label: Address, optional: true }
|
||||
```
|
||||
|
||||
```yaml
|
||||
# aggregates.yaml
|
||||
aggregates:
|
||||
- bucket: leads
|
||||
initial: open
|
||||
states:
|
||||
open: { event: received }
|
||||
quoted: { event: quoted }
|
||||
won: { event: won }
|
||||
lost: { event: lost }
|
||||
transitions:
|
||||
open: [quoted, lost]
|
||||
quoted: [won, lost]
|
||||
```
|
||||
|
||||
```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
|
||||
- { to: lost, label: Not for us }
|
||||
- { from: quoted, to: won, label: Won }
|
||||
- { from: quoted, to: lost, label: Lost }
|
||||
```
|
||||
|
||||
## House rules for the copy
|
||||
|
||||
- The page name is a question the visitor could ask themselves.
|
||||
- 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 actually use to
|
||||
answer. If they can't say what they do with it, it's optional or
|
||||
gone.
|
||||
- A followup page says who reads it, when they answer, and what the
|
||||
visitor can do meanwhile.
|
||||
|
||||
## Engines
|
||||
|
||||
Any engine that reads a task's `state` and `questions` and returns
|
||||
`{"id","choice","confidence"}` per line works. Compare at least two
|
||||
before trusting a score, and re-run the same engine before trusting a
|
||||
change: one run of eight tasks is an anecdote. See the repo README for
|
||||
what has been measured about each.
|
||||
Reference in New Issue
Block a user