The Companion-as-interface principle says the Operator changes settings, and increasingly operates the system, by talking to the Companion. That principle has a hole: a settings screen shows you what you can change, but a conversation shows you nothing until you ask — and you can't ask for what you don't know exists. An Operator could sit in front of the Companion never knowing the wait time is adjustable, or never knowing what they're able to do in the room they're in.
This capability fills that hole. The Operator can ask the Companion "what can I do here?" or "what can I change?" or "I'm lost — help," and the Companion answers with what's available in the current context — the actions this room offers and the settings this surface exposes — and can give examples of what to say to actually do each one. When the Operator asks for something close-but-not-quite (a setting by a name the Companion doesn't recognize), the Companion tells them what is available rather than just refusing.
The design holds three disciplines. It is pull, not push — the Companion answers when asked; it does not nag with unprompted lists (so the seed's "only show what is available" holds — nothing is shown unfilled). It is context-scoped — the answer is about here, not a global catalog dump. And it is generated, never hand-written — the list of what's available comes from the system's own registries, so it can never go stale the way the current (incomplete, hand-typed) settings list already has.
v0.1 was written from the two-part grounding (whether discovery existed). The Step 0 inspection then verified the exact shapes the change request will modify, and surfaced three drift items. v0.2 folds them in; the v0.1 assumptions they correct are named, not erased.
D2 (a real design wrinkle — corrects §3.2). v0.1's §3.2 assumed a clean "current surface" to scope the answer to. Step 0 showed the converse call sites give three coarse surfaces (in-engagement, navigation, create-engagement), but settings ownership is finer: from the in-engagement surface only conversation.message_order is owned locally — blur and silence_submit_seconds are owned by the engagement-navigation voice panel, a different component. Strict converse-surface scoping would therefore tell an in-engagement Operator only about message order and hide the two voice settings behind a component boundary the Operator does not perceive — reopening, at a finer grain, the very discoverability hole this capability exists to close. Resolution (Operator-confirmed): scope to the perceived engagement-context, not the technical component. An Operator working inside an engagement perceives one place ("the engagement I'm in"), not two components; orientation scopes to that perceived context and surfaces every setting reachable from it (message order and the voice settings alike), locating each lightly so the Operator knows where ("in voice listening you can adjust blur and the wait time"). This honors "only show what is available" correctly: blur is available to an in-engagement Operator (reached via the voice panel), so surfacing it is right and hiding it would be the failure. §3.2 below carries the corrected framing.
D1 (a sizing correction — corrects §3.4 and §6). v0.1's §3.4 said "generalize suggested_actions rendering, preserving the creation case as one instance," implying reuse of an existing mount. Step 0 showed SuggestedActions.tsx is mounted only on the create-engagement surface (ChatView) — it renders zero times on CenterPane (the in-engagement surface, the most likely place an Operator asks "what can I do here") and zero times on the navigation surface. So generalizing is not reuse-an-existing-mount; the response field and the component exist, but the mount on the orientation-relevant surfaces does not. The change request adds the SuggestedActions render to CenterPane and the navigation reply surface — new render work, not reuse. §3.4 and §6 below carry this.
D3 (a favorable simplification — affects §6). Adding the orientation intent does not require a separate intent_hint edit: intent_hint already derives from IntentLabel (the Phase 55 enumeration collapse), so widening the IntentLabel Literal auto-covers it. The declare-and-register touchpoints for the new intent are therefore: the IntentLabel Literal + intent_classifier.md + intent_instructions/<intent>.md + the router.py dispatch branch + the _route_<intent> handler. One fewer edit than v0.1 implied.
Everything else in v0.1 was verified correct by Step 0: no enumeration function over KNOWN_SETTINGS (piece 1 net-new); no canonical display name (the CR adds one); no room→action mapping anywhere (piece 2 net-new); general_conversation is the current catalog-blind landing (piece 3); unknown_setting is the 2-of-3 hardcoded list (piece 4); ConverseRequest has no surface field and suggested_actions is list[str] (piece 5).
The Companion-as-Operator-system-interface standing note v0.1 commits Loomworks to conversation as the primary surface for Operator-system interaction. That commitment is incomplete without discoverability: a conversational interface that cannot tell the Operator what it offers leaves the Operator unable to use it. A settings screen is self-advertising (the controls are visible); a conversation is not (nothing is visible until asked, and you cannot ask for the unknown). So the standing note's principle obligates this capability — it is the discovery surface the principle assumes but does not itself provide.
The triggering observation: "the conversation is the interface" needs a way for the Operator to know which settings are available and how to modify them — and, more broadly, an Operator who is lost should be able to engage Loomworks to identify what to do and ask for examples.
Scope. The capability answers, in the current context: - What can I do here? — the actions available in this room/surface (contribute, organize, shape, render, adjust, and the rest, scoped to context). - What can I change here? — the settings adjustable in this context (the "adjust" category; subsumes the settings-discovery case). - I'm lost / what's next? — orientation: what a sensible next step is, given the current state. - Show me / for example? — drive-it examples: what the Operator could say to the Companion to do each available thing ("you could say: 'add a note that…'").
The gap. No discovery or help intent exists. The 20-intent classifier taxonomy is all action intents plus one catch-all (general_conversation), which has zero awareness of available actions or settings — a "what can I do?" today lands there and is answered conversationally with no catalog knowledge (it would deflect or invent). The one place capabilities are ever named — tune_setting's unknown_setting response — is hardcoded prose, lists only 2 of the 3 settings (it has already gone stale), and is reachable only by a failed tune attempt.
The two foundations to build on, not around.
- suggested_actions — the ConverseResponse field and its Operator-Layer renderer (SuggestedActions.tsx) are built end-to-end and state-aware, but wired only into the engagement-creation flow with hardcoded strings, empty on every other path. This is the surfacing pipe to generalize.
- tryPromptHint — static "you could say…" tooltips already exist ("draft a scoping note," "what should I do next"). Primitive (static, tooltip-only, and — tellingly — one suggests "what should I do next" while nothing answers it), but the seed of the examples half.
The three shared missing pieces (settings discovery and orientation fail on the same three):
1. No capability-query intent.
2. No enumerable, context-scoped catalog (settings exist as KNOWN_SETTINGS but unsurfaced; room-actions are not enumerated at all).
3. No surface-context signal on ConverseRequest.
These were settled with the Operator before scoping; recorded here with their reasoning and the alternatives set aside.
Settings discovery is one category of orientation — the "adjust" category alongside contribute / organize / shape / render. A single capability-query/orientation intent handles both: "what can I change" returns the adjust-category slice; "what can I do" returns the whole context-scoped catalog.
Set aside: two separate intents (a settings-discovery intent and an orientation intent). Rejected because it rebuilds the same machinery (intent + catalog + scoping + surfacing) twice for what is one question at different granularities.
The answer must be scoped — "what can I do here," not a global dump — but here means the perceived engagement-context, not the technical component. ConverseRequest carries project_id (which engagement) but no surface signal, so this requires a new surface-context field on ConverseRequest, populated by the Operator Layer from the active surface. The field carries the perceived context (which engagement-surface-group the Operator is in), and the engine's scoping maps that to everything reachable from that context — not only what is owned by the exact component.
Why perceived-context, not strict-surface (the Step 0 correction): the converse call sites give three coarse surfaces (in-engagement, navigation, create-engagement), but settings ownership is finer — from the in-engagement surface only conversation.message_order is owned locally, while blur and silence_submit_seconds are owned by the engagement-navigation voice panel. Strict converse-surface scoping would tell an in-engagement Operator only about message order and hide the two voice settings behind a boundary the Operator does not perceive — reopening the discoverability hole at a finer grain. So orientation scopes to the place the Operator perceives ("the engagement I'm in"), surfaces every setting and action reachable from it, and locates each lightly so the Operator knows where: "you can change the message order here; in voice listening you can adjust the blur and the wait time."
Why it's load-bearing, not optional: the seed's "only show what is available" commitment requires scoping — but read correctly. An unscoped answer dumps all ~20 capabilities regardless of relevance (violating the principle in spirit). A strict-surface answer hides available-but-elsewhere capabilities (the D2 failure). The perceived-context answer is the one that honors the principle: it shows what is available to the Operator from where they are (blur is available in-engagement — reached via the voice panel — so surfacing it is right; hiding it would be the failure), and nothing that isn't.
Set aside (1): deferring scoping (engine answers "everything"). Rejected — much weaker, and dumps the irrelevant. Set aside (2): strict converse-surface scoping (only what the exact component owns). Rejected per D2 — it hides reachable settings behind a perceived-as-single boundary, defeating discoverability.
The catalog answer is generated from source registries, never restated in prose. This is the spine, and it is the direct lesson of the unknown_setting defect: hand-written capability text rots — the current settings list drifted to 2-of-3 the moment a third setting landed.
Two source registries feed the catalog:
- Settings (the "adjust" category) — from KNOWN_SETTINGS, extended with a canonical display name per setting (none exists today; names live as a synonym list in _LABEL_TO_KEY, none marked primary). A small enumeration helper joins key → canonical-name → bounds → conversation-synonyms.
- Room-actions (the rest) — a new room→actions registry (net-new; does not exist). The 20-intent taxonomy is the raw material, but it is a flat classifier list with no room-scoping, no descriptions, and no example phrasings. The registry maps each room/surface to the actions available there, each carrying a plain-language description and one or more drive-it example phrasings.
Set aside: a hand-maintained catalog document. Rejected — it is exactly the thing that already rotted; the registry is the single source so the answer cannot go stale.
suggested_actions — generalizing the field, adding the mounts (corrected per Step 0 / D1)The orientation answer populates suggested_actions (now context-aware and generated), rendered by the existing SuggestedActions.tsx component. The response field (list[str]) and the component are already built and shape-general — but the component is mounted only on the create-engagement surface (ChatView); it renders zero times on CenterPane (the in-engagement surface, where an Operator most likely asks "what can I do here") and zero times on the navigation surface. So "generalize" is two distinct kinds of work: (a) generalize the population (the engine sets suggested_actions on the orientation path, not only the creation path), and (b) add the render mount to the orientation-relevant surfaces — CenterPane and the navigation reply surface. The component is reused; the mounts on those surfaces are new render work, not reuse.
The widening, named: suggested_actions today means "next steps in the creation flow." This widens it to "what you can do here." That is the right widening — next-steps and what-you-can-do are the same family — but it changes the field's meaning and touches the creation flow's existing behavior, so the CR must preserve the creation-flow population (_suggested_actions_for, the format-choice and commit-override actions) as one case of the general mechanism, not break it.
Set aside: a new dedicated discovery-response channel. Rejected — the field and component already work; reusing them (and adding the missing mounts) is less than building a parallel channel.
"Show me / for example" yields drive-it examples — what the Operator could say to the Companion to perform an available action ("you could say: 'add a note that the client prefers email'"), not examples of finished work-products. This teaches the Operator to drive Loomworks.
The examples live in the room-actions registry (§3.3) — each action carries its example phrasing(s) as registry data, so they are generated with the catalog, not hand-written per surface (the failure mode the static tryPromptHint tooltips show — coarse, per-surface, disconnected from any handler). The existing tryPromptHint strings are the seed: the build migrates their content into the registry as the first examples and wires them to the answering handler, so "what should I do next" (which a tooltip already suggests) becomes a question the Companion actually answers.
The queued-directions §6.4 Explain affordance is orientation's sibling, not its parent. Both belong to the discipline-plus-recovery family (every UX discipline that reduces a gap needs a recovery affordance for the residue). But they address different gaps: - Explain (§6.4) — comprehension recovery: "what did you mean by that?" (the Operator didn't grasp a Companion utterance). - Orientation (this note) — capability discovery: "what can I do here?" (the Operator doesn't know what's available).
§6.4's anticipated sibling list does not include orientation; the record had no orientation entry. This note records orientation as the new sibling and follows §6.4's shape (the affordance model below).
Why orientation builds ahead of §6.4, which is deferred. §6.4 is gated on Phase-32 lived-Operator calibration evidence because its hard problem is voice composition — phrasing a clarification on-discipline, which needs real-use data. Orientation's hard problem is different and more mechanical: enumeration — knowing what's available here, which is knowable from the code today and needs no calibration evidence to be correct. The catalog is a registry problem, not a voice problem. (The examples half has a voice element, but it is registry-authored drive-it phrasings, lower-stakes than Explain's clarification composition.) So orientation is buildable now in a way Explain is not. This was a flagged, Operator-confirmed decision: build orientation ahead of its sibling.
Affordance model (following §6.4). §6.4 anticipates button mode (text substrate) and audible mode (voice substrate). Orientation follows the same: the primary build is text/conversational (ask in the conversation, answered via suggested_actions) — no voice-modality substrate required, matching §6.4's button-mode-first candidate. An audible "what can I do here" rides the shared voice-modality substrate later, alongside §6.4's audible mode, mobile presence, and quick-capture — voice-modality work compounds across all of them. This note scopes the text/conversational build; audible is a later voice-modality-arc addition.
This is an engine + Operator-Layer build. Proposed CR structure (to be settled in the CR itself):
Engine:
1. Settings enumeration helper — a function over KNOWN_SETTINGS returning the catalog (key, canonical display name, bounds, synonyms). Add a canonical display name per setting.
2. Room-actions registry — net-new: room/surface → available actions, each with a plain-language description and drive-it example phrasing(s). Seeded from the intent taxonomy + the migrated tryPromptHint content.
3. Orientation intent + handler — a new capability-query intent in the classifier; a handler that assembles the context-scoped catalog (settings slice + room-actions slice) and populates suggested_actions. Handles the full-catalog ("what can I do"), the adjust-slice ("what can I change"), and the orientation/next-step ("I'm lost / what's next") cases. (Declare-and-register touchpoints, per Step 0 / D3: the IntentLabel Literal + intent_classifier.md + intent_instructions/<intent>.md + the router.py dispatch branch + the _route_<intent> handler. No separate intent_hint edit — intent_hint derives from IntentLabel, so widening the Literal covers it.)
4. unknown_setting fix — repoint tune_setting's near-miss response at the enumeration helper so it names all current settings, generated. (This is the staleness defect; see §7 — it ships as part of this build, and the helper it needs is piece 1.)
5. Surface-context field on ConverseRequest — so the handler can scope to the perceived engagement-context (§3.2). The field's enumerated values follow the three converse call sites (in-engagement, navigation, create-engagement); the handler maps the perceived context to all settings/actions reachable from it, not only those owned by the exact component (the D2 correction).
Operator Layer:
6. Pass surface context — populate the new ConverseRequest field at all three converse call sites (CenterPane in-engagement, engagement-navigation/page navigation, ChatView create-engagement), each from its known active surface.
7. Generalize suggested_actions — population and mounts (per Step 0 / D1) — (a) the orientation answer populates suggested_actions on the orientation path (engine piece 3), and (b) add the SuggestedActions.tsx render mount to CenterPane and the navigation reply surface (today it is mounted only on ChatView/create-engagement — it renders zero times on the orientation-relevant surfaces). The component is reused; the mounts are new. Preserve the creation-flow population unchanged.
8. Migrate tryPromptHint content into the registry (engine piece 2) and wire the tooltips' suggested utterances to the now-answering handler — so "what should I do next" (which a tooltip already suggests) becomes a question the Companion actually answers.
Sequencing within the CR: engine pieces 1–2 (the registries/helpers) are the foundation; piece 4 (the unknown_setting fix) can land early since it only needs piece 1; pieces 3 + 5 are the capability proper; OL pieces 6–8 surface it. The CR will sequence per-step with halts, the API-grind rhythm.
The unknown_setting staleness is a live defect independent of the larger capability: the Companion currently tells Operators an incomplete list of adjustable settings (2 of 3), and will keep rotting as settings are added. The fix (engine piece 4) makes it generate from KNOWN_SETTINGS via the enumeration helper (piece 1), so it cannot rot again. It is called out separately so that, if the Operator ever wants the defect fixed ahead of the full capability, it can be pulled forward as a one-piece correction — but as planned it ships within this build, since it shares the enumeration helper.