Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions docs/devteam/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ <h2>Loop <span class="sub">how the supervisor notices, makes sense of, decides a
<div class="loop-panel" data-panel="orient" hidden>
<p class="loop-intro">What the signal adds up to. Things noticed more than once become recurring signals; real or likely trouble becomes a problem; lessons, facts and your preferences become knowledge, which goes into every agent's instructions once accepted.</p>
<h3>Orientation debt</h3>
<p class="small muted">Recurring signals nothing has explained for more than a day: each wants a lesson, a problem, or a ruling that it is noise.</p>
<p class="small muted">Recurring signals nothing has explained for more than a day: each wants a lesson, a problem, or a ruling that it is noise. Then incidents fixed and verified in the last 30 days that nobody has learned from: each wants a lesson, or a ruling that there is nothing to keep.</p>
<div id="debt"></div>
<div class="two">
<div><h3>Recurring signals</h3><div id="clusters"></div></div>
Expand Down Expand Up @@ -2106,8 +2106,10 @@ <h4><a class="title-link" href="#goal/${encodeURIComponent(g.id)}">${esc(g.title

// Orient
const debt = o.debt?.signals || [];
$('debt').innerHTML = debt.length ? debt.map((x) => `
<div class="card small" data-open="signal/${esc(encodeURIComponent(x.key))}"><a class="linkish" href="#signal/${encodeURIComponent(x.key)}"><b>${esc(signalTitle(x.key))}</b></a> <span class="muted">· seen ${esc(x.count)} times · unexplained for ${esc(hoursWords(x.hours))}</span><div>${linkify(x.latest)}</div></div>`).join('') : empty('Nothing waiting: every recurring signal is explained or under a day old.');
const taught = o.debt?.incidents || [];
$('debt').innerHTML = debt.length || taught.length ? debt.map((x) => `
<div class="card small" data-open="signal/${esc(encodeURIComponent(x.key))}"><a class="linkish" href="#signal/${encodeURIComponent(x.key)}"><b>${esc(signalTitle(x.key))}</b></a> <span class="muted">· seen ${esc(x.count)} times · unexplained for ${esc(hoursWords(x.hours))}</span><div>${linkify(x.latest)}</div></div>`).join('') + taught.map((x) => `
<div class="card small"${subj('problem', x.id, x.title)} data-open="problem/${esc(encodeURIComponent(x.id))}"><span class="badge">incident</span><a class="linkish" href="#problem/${encodeURIComponent(x.id)}"><b>${esc(x.title)}</b></a> <span class="muted">· fixed and verified ${x.days ? `${esc(x.days)} day${x.days > 1 ? 's' : ''} ago` : 'today'}</span><div>What did it teach? A lesson, or a ruling that there is nothing to keep. <span class="muted">${x.decision ? `The fix: ${linkify(x.decision)} · ` : ''}what came of it: ${linkify(x.outcome)}</span></div></div>`).join('') : empty('Nothing waiting: every recurring signal is explained or under a day old, and every fixed incident has been learned from.');
$('clusters').innerHTML = o.clusters.length ? o.clusters.map((c) => `
<div class="card small" data-open="signal/${esc(encodeURIComponent(c.key))}"><a class="linkish" href="#signal/${encodeURIComponent(c.key)}"><b>${esc(words(c.key.split(':')[0]))}</b></a> ${subjectLink(c.key.split(':').slice(1).join(':'))} <span class="muted">· seen ${esc(c.count)} times</span><div>${linkify(c.latest)}</div></div>`).join('') : empty('None. Something noticed more than once shows here.');
$('problems').innerHTML = o.problems.length ? o.problems.map((p) => `
Expand Down Expand Up @@ -2290,8 +2292,8 @@ <h4><a class="title-link" href="#goal/${encodeURIComponent(g.id)}">${esc(g.title
hide: ['review', 'result', 'secrets', 'secrets_posted', 'choices', 'status'],
fmt: { why_asked: (w) => `${w.rung ? `${esc(RUNG_WORDS[w.rung])}` : ''}${w.ceiling ? `, at most ${esc(RUNG_WORDS[w.ceiling])}` : ''}${(w.reasons || []).length ? ` · ${esc(w.reasons.map(words).join(', '))}` : ''}`, autonomy: (a) => `${esc(words(a.class))}: ${esc(RUNG_WORDS[a.rung])}${a.scope ? ` (${esc(a.scope)})` : ''}`, goal: (v) => glink(v), re: (v) => linkify(v) },
},
decision: { order: ['what', 'class', 'scope', 'authority', 'approved_by', 'rung', 'ceiling', 'options', 'rationale', 'confidence', 'refs', 'reversible'], fmt: { authority: (v) => esc(AUTHORITY_LABEL[v] || v) } },
act: { order: ['what', 'class', 'scope', 'decision', 'approved_by', 'rung', 'refs', 'reversible'], fmt: { id_block: (b) => `${esc(b.feature)}: ${esc(b.from)} to ${esc(b.to)}` } },
decision: { order: ['what', 'class', 'scope', 'authority', 'approved_by', 'knowledge', 'rung', 'ceiling', 'options', 'rationale', 'confidence', 'refs', 'reversible'], label: { knowledge: 'Rests on' }, fmt: { authority: (v) => esc(AUTHORITY_LABEL[v] || v), knowledge: linkList } },
act: { order: ['what', 'class', 'scope', 'decision', 'approved_by', 'rung', 'refs', 'knowledge', 'reversible'], label: { knowledge: 'Given' }, fmt: { id_block: (b) => `${esc(b.feature)}: ${esc(b.from)} to ${esc(b.to)}`, knowledge: linkList } },
knowledge: { order: ['statement', 'kind', 'status', 'scope', 'confidence', 'valence', 'evidence'], fmt: { valence: (v) => (v < 0 ? `${esc(v)} (something to avoid)` : esc(v)) } },
problem: { order: ['title', 'status', 'severity', 'realized', 'threatens', 'assigned_to', 'detail', 'refs'], fmt: { realized: (v) => (v ? 'yes: an issue' : 'no: a risk'), assigned_to: (v) => linkify(v), severity: (v) => `<span class="badge ${sevBadge(v)}">${esc(v)}</span>` } },
message: { fmt: { to: (v) => esc(who(v)), re: (v) => linkify(v) } },
Expand Down Expand Up @@ -2922,6 +2924,7 @@ <h3>Changelog</h3>${merged.length ? `<ul>${merged.map((t) => `<li>${tlink(t.id)}
] })}
${v.detail ? `<p class="ctx measure">${v.detail}</p>` : ''}
${lineageBlock(p)}
${knowledgeCited(p, 'What its instructions carried')}
${d ? `<h3>The decision it carried out</h3><div class="card small"><div>${dlink(d.id, d.what)}</div>${d.rationale ? `<div class="act-detail">${linkify(d.rationale)}</div>` : ''}<div class="meta">${authorityWords(d.under)} · ${esc(when(d.at))}${d.correction ? ` · <span class="badge high">corrected</span> ${esc(d.correction.reason)}` : ''}</div></div>` : ''}
${citedBlock(p, a)}
${a.correction ? '' : a.decision ? '<p class="small muted">It carried out a decision: correct the decision if the call was wrong (Loop → Decide). If the call was sound and the act went wrong, that is a problem, not a trust drop.</p>' : '<p class="small muted">To correct it, use Correct on it in Loop → Act.</p>'}
Expand All @@ -2934,9 +2937,16 @@ <h3>Changelog</h3>${merged.length ? `<ul>${merged.map((t) => `<li>${tlink(t.id)}
const k = (DATA.ooda.knowledge || []).find((x) => x.id === approvedBy);
return k ? `<h3>The standing rule it rests on</h3><div class="card small"><a class="linkish" href="#lesson/${encodeURIComponent(k.id)}">${esc(k.statement)}</a> <span class="muted">· ${esc(scopeWords(k.scope))}</span></div>` : '';
}
// Records an object cites, in their own words (from its peek).
// The knowledge a decision rests on (decide --on), or a start's prompt carried, in its own words
// (from its peek): what the call or the session had to go on.
function knowledgeCited(p, title) {
const ks = p?.record?.fields?.knowledge || [];
return ks.length ? `<h3>${esc(title)}</h3><ul>${ks.map((id) => { const x = p.cited?.[id]; return `<li>${linkify(id)}${x ? ` <span class="muted">${esc(words(x.kind ?? x.type))}${x.type === 'knowledge' && x.status && x.status !== 'accepted' ? `, ${esc(x.status)}` : ''}</span>${x.text ? `: ${linkify(String(x.text).slice(0, 160))}` : ''}` : ''}</li>`; }).join('')}</ul>` : '';
}
// Records an object cites, in their own words (from its peek); what it rests on or was given has its own list.
function citedBlock(p, o) {
const c = Object.entries(p?.cited || {}).filter(([id]) => !id.startsWith('q-') || !o?.approved_by || id !== o.approved_by);
const given = new Set(p?.record?.fields?.knowledge || []);
const c = Object.entries(p?.cited || {}).filter(([id]) => (!id.startsWith('q-') || !o?.approved_by || id !== o.approved_by) && !given.has(id));
return c.length ? `<h3>What it cites</h3><ul>${c.map(([id, x]) => `<li>${linkify(id)} <span class="muted">${esc(words(x.type))}${x.kind ? ` (${esc(words(x.kind))})` : ''}</span>${x.text ? `: ${linkify(String(x.text).slice(0, 160))}` : ''}</li>`).join('')}</ul>` : '';
}
function openDecision(id) {
Expand All @@ -2953,6 +2963,7 @@ <h3>Changelog</h3>${merged.length ? `<ul>${merged.map((t) => `<li>${tlink(t.id)}
] })}
${lineageBlock(p)}
${restsOn(r.under?.approved_by)}
${knowledgeCited(p, 'What it drew on (not its authority)')}
${r.kind === 'decision' ? `<h3>Carried out by</h3>${acts.length ? `<ol class="tl">${acts.map((a) => `<li class="act"><time>${a.created_at ? esc(when(a.created_at)) : ''}</time><a class="linkish" href="#act/${encodeURIComponent(a.id)}">${a.what ? actView(a).head : esc(a.id)}</a></li>`).join('')}</ol>` : '<p class="muted">Nothing yet.</p>'}` : ''}
${r.correction ? '' : '<p class="small muted">To correct it, use Correct on it in Loop → Decide.</p>'}
${historySection(p, id)}`);
Expand Down
4 changes: 2 additions & 2 deletions docs/devteam/process/supervisor.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ A reading (a sensor's daily rollup, a goal reading, a `spend` line, a `gate` run

**Orient**
3. Fix drift (§4).
4. `devteam.mjs orient`: every recurring signal (three or more observations or corrections on one key) gets explained: a lesson (`knowledge add --kind lesson --scope … --evidence …`, with `--kit` when it is about the kit rather than this project, §9 Share upstream), a problem (`problem add`), or noise: `decide --class orient-noise --refs <signal key> <observation ids> -- "<why>"` (it clears the signal; the owner can correct it, and a corrected ruling brings the signal back). A sensor's qualifies and spikes on one fingerprint are one signal, keyed by the fingerprint's id. A ruling that names the signal's key covers all of its observations up to the ruling, so it needn't list them; one after it gets a fresh look (use `sensor ignore` for a fingerprint that is always noise). A fingerprint an open incident holds is in hand, not a signal. A candidate lesson waits in the owner's inbox to keep or retire; accept one yourself (`knowledge set <id> --status accepted`) only when the evidence is plain, and the owner can still retire it. Accepted knowledge goes into the next prompts in that scope. Triage open problems: assign, mitigate, resolve. Read the owner's recent resolutions (`log --type observation`, kind `thread-resolved`): what they keep raising, about which goals and pages, and what they resolved without a change. Three threads on one goal is a signal about the goal: orient on it. For each new problem or incident, and each observation of severity 3 or more, `search "<its words>" --limit 5`: it may have happened before under other ids. A recurring signal nothing has explained for more than a day is orientation debt: the dashboard's Orient tab lists it first (`dashboard-data`'s `ooda.debt`), and you pay it down before newer signals. Each record in the loop has a lineage, what it came from and what came of it (observation → signal → lesson or incident → decision → act → outcome): `devteam.mjs peek <kind> <id> --json` carries it as `lineage`, and `peek <kind> <id> --mermaid` prints it as a Mermaid flowchart to paste into a retro or a PR body (a recurring signal's kind is `signal`, its id its key).
4. `devteam.mjs orient`: every recurring signal (three or more observations or corrections on one key) gets explained: a lesson (`knowledge add --kind lesson --scope … --evidence …`, with `--kit` when it is about the kit rather than this project, §9 Share upstream), a problem (`problem add`), or noise: `decide --class orient-noise --refs <signal key> <observation ids> -- "<why>"` (it clears the signal; the owner can correct it, and a corrected ruling brings the signal back). A sensor's qualifies and spikes on one fingerprint are one signal, keyed by the fingerprint's id. A ruling that names the signal's key covers all of its observations up to the ruling, so it needn't list them; one after it gets a fresh look (use `sensor ignore` for a fingerprint that is always noise). A fingerprint an open incident holds is in hand, not a signal. A candidate lesson waits in the owner's inbox to keep or retire; accept one yourself (`knowledge set <id> --status accepted`) only when the evidence is plain, and the owner can still retire it. Accepted knowledge goes into the next prompts in that scope. Triage open problems: assign, mitigate, resolve. Read the owner's recent resolutions (`log --type observation`, kind `thread-resolved`): what they keep raising, about which goals and pages, and what they resolved without a change. Three threads on one goal is a signal about the goal: orient on it. For each new problem or incident, and each observation of severity 3 or more, `search "<its words>" --limit 5`: it may have happened before under other ids. A recurring signal nothing has explained for more than a day is orientation debt: the dashboard's Orient tab lists it first (`dashboard-data`'s `ooda.debt.signals`), and you pay it down before newer signals. So is an incident fixed and verified in the last 30 days (resolved, with an outcome citing it) that nothing learned from: `orient` lists it after the signals ("<p-id> was fixed and verified (<title>): what did it teach?", `ooda.debt.incidents`) with the two answers to paste: a lesson citing the incident, its fix decision and the outcome (`knowledge add --kind lesson --scope … --evidence <p-id> <d-id> <outcome id>`), or `decide --class orient-noise --refs <p-id> -- "nothing to keep"`. A fast fix still turns into knowledge this way; an incident whose status the owner set is theirs, and owes nothing. Each record in the loop has a lineage, what it came from and what came of it (observation → signal → lesson or incident → decision → act → outcome): `devteam.mjs peek <kind> <id> --json` carries it as `lineage`, and `peek <kind> <id> --mermaid` prints it as a Mermaid flowchart to paste into a retro or a PR body (a recurring signal's kind is `signal`, its id its key).
- **Revisit what matters.** You rate; the kit orders. A goal with no impact (`goal-unjudged`), or one whose impact was corrected at a retro: `goal impact <g-id> <1-4> --rationale "…"`, judged against the strategy's "How it wins" order and never against how much work the goal is (`planning.md` §1). Judge again when the strategy changes. An open question whose stake or urgency has changed (work stopped on it, a ticket merged, another question now covers it): `rate <qid> --urgency 3`. A new question that an open one would settle: `rate <gate-qid> --settles <qid>`, which adds it to the list. An unrated question (`question-unrated`): rate it.

**Decide**
Expand All @@ -44,7 +44,7 @@ A reading (a sensor's daily rollup, a goal reading, a `spend` line, a `gate` run

**Act**
8. Schedule (§5). Merge (`merge <id>`, §5). Log every decision; approvals of specs and plans are acts of those classes, not commands of their own (`act --class approve-spec --refs <id>`, `act --class approve-plan --refs <id>`; the latter refuses while a one-way door on the ticket is unanswered, or while the committed plan has no TL;DR; a door the owner already answered for the ticket's goal is cited with `--approved <qid>`, and counts only when the plan review says `- Goal-level door: <qid> covers this plan`), and so are rulings (`act --class contract-ruling`, `act --class approve-design-change`), goal readiness, recoveries and trials. On a `risk: normal` ticket, approve a plan from its TL;DR and its plan review (the verdict holds, its findings are dispositioned) unless one of them gives you a reason to read on; on `risk: high`, or a ticket with a one-way door or a contract, read the whole plan. `merge`, `ticket add`/`set`, `answer --defaulted` and `withdraw` log their own acts.
- **Decide, then act.** A judgment call (you weighed options; a reasonable owner could have chosen otherwise) is a decision first: `decide --class <c> --refs <what it is about> [--options "a" "b"] [--rationale "…"] [--confidence 0-1] [--approved <qid|kid>] -- "<what you decided>"` prints a `d-` id and records the trust it rests on. Each act that carries it out cites it: `act --decision <d-id> -- "<what you did>"`, within the decision's refs; `merge`, `ticket add`/`set`, `withdraw`, `goal hold`, `worker`, `planner` and `fleet` take `--decision` too when what they act on is in its refs. Something routine (merging what the gates passed, starting a worker for a ready ticket) is an act on its own: `act --class <c> [--refs ids] -- "<what>"`. A standing rule authorises a call only when the owner scoped it to the class (`class:<c>`). A decision made on the class's own rung lapses if the class drops after it: decide again.
- **Decide, then act.** A judgment call (you weighed options; a reasonable owner could have chosen otherwise) is a decision first: `decide --class <c> --refs <what it is about> [--options "a" "b"] [--rationale "…"] [--confidence 0-1] [--approved <qid|kid>] -- "<what you decided>"` prints a `d-` id and records the trust it rests on. When the call rests on what the project knows, cite it: `--on <k-…|p-…>…` names the accepted knowledge (a lesson, a fact, the owner's rule) or the problems it turned on (at most 60); knowledge that isn't accepted is refused, and it grants nothing: the authority is still `--approved` or the class's rung. They are stored as the decision's `knowledge`, never in its refs (an act stays within refs) and never as its authority (`--approved` is that); a candidate or retired lesson, or an id also in `--refs`, is refused. The lineage draws them into the decision. A worker's or planner's start records the accepted knowledge its prompt carried on its `worker-slots` act (`knowledge`), so a lesson's effect can be traced to the sessions it reached. Each act that carries it out cites it: `act --decision <d-id> -- "<what you did>"`, within the decision's refs; `merge`, `ticket add`/`set`, `withdraw`, `goal hold`, `worker`, `planner` and `fleet` take `--decision` too when what they act on is in its refs. Something routine (merging what the gates passed, starting a worker for a ready ticket) is an act on its own: `act --class <c> [--refs ids] -- "<what>"`. A standing rule authorises a call only when the owner scoped it to the class (`class:<c>`). A decision made on the class's own rung lapses if the class drops after it: decide again.
9. Health: a ticket silent for `stale_after_minutes` whose pane shows nothing goes to recovery.

**Improve and record**
Expand Down
18 changes: 18 additions & 0 deletions docs/devteam/schema/log.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1784,6 +1784,15 @@
"type": "string",
"pattern": "^[qk]-"
},
"knowledge": {
"description": "The knowledge or problems this decision rests on (decide --on); never copied into refs, and never its authority (approved_by is).",
"type": "array",
"items": {
"type": "string",
"pattern": "^(k|p)-"
},
"x-tip": "The lessons, facts or problems the call rests on."
},
"corrected": {
"description": "Your correction, if the call was wrong; it drops the class to rung 1. On a goal-impact decision (constitutional, so no rung drops) it may carry the impact you give the goal instead.",
"type": "object",
Expand Down Expand Up @@ -1871,6 +1880,15 @@
"pattern": "^d-",
"x-tip": "The decision it carried out."
},
"knowledge": {
"description": "On a worker-slots act that started a worker or planner fresh: the accepted knowledge its prompt carried (the knowledge that applies to its ticket or feature), newest first, at most 60. Never in what, which the kit parses. A resumed session reads no new prompt, so its act carries none.",
"type": "array",
"items": {
"type": "string",
"pattern": "^k-"
},
"x-tip": "The lessons and facts its instructions carried when it started."
},
"corrected": {
"description": "Your correction, if it was its own call and wrong.",
"type": "object",
Expand Down
Loading
Loading