# Dominds Dialog System Implementation

Chinese version: [中文版](./dialog-system.zh.md)

This document provides detailed implementation specifications for the Dominds dialog system, including core tools, technical architecture, dialog management, memory management, system integration, and the Questions for Human (Q4H) mechanism.

## Table of Contents

1. [Terminology](#terminology)
2. [Backend-Driven Architecture](#backend-driven-architecture)
3. [3-Type Teammate Tellask Taxonomy](#3-type-teammate-tellask-taxonomy)
4. [Core Mechanisms](#core-mechanisms)
5. [Q4H: Questions for Human](#q4h-questions-for-human)
6. [Dialog Relationship & SideDialogs](#dialog-relationship--sidedialogs)
7. [Mental Clarity Tools](#mental-clarity-tools)
8. [Reminder Management](#reminder-management)
9. [SideDialog Registry](#sideDialog-registry)
10. [Technical Architecture](#technical-architecture)
11. [Dialog Management](#dialog-management)
12. [Memory Management](#memory-management)
13. [System Integration](#system-integration)
14. [State Diagrams](#state-diagrams)
15. [Complete Flow Reference](#complete-flow-reference)

---

## Terminology

This chapter defines the implementation-facing terms used throughout this document.
For bilingual / user-facing naming conventions (Main Dialog / Side Dialog; tellasker / tellaskee), see [`dominds-terminology.md`](./dominds-terminology.md).
For Taskdoc package structure and encapsulation rules, see [`encapsulated-taskdoc.md`](./encapsulated-taskdoc.md).

### AskerDialog

An **askerDialog** is the implementation-facing dialog that currently owns an assignment or reply obligation for a sideDialog. It may be the main dialog or another sideDialog; it is a tellask/reply relationship, not inherently a hierarchy label.

Note: **askerDialog** is a tellask/reply relation, not a hierarchy or seniority label. For TYPE A (`tellaskBack`), the tellasker is the direct askerDialog; for TYPE B/C, the tellasker may be a different dialog.

An askerDialog may receive **TellaskBack** from sideDialogs that currently owe it a reply. When a sideDialog needs guidance or additional context, it can Tellask back via `tellaskBack({ tellaskContent: "..." })` (TYPE A / `TellaskBack`), which provides responses that feed back into the sideDialog's context.

### SideDialog

A **sideDialog** is a specialized dialog spawned by an askerDialog to handle specific subtasks. SideDialogs operate with fresh context, focusing on targeted objectives while maintaining a communication link back to their askerDialog.

**TellaskBack**: A sideDialog can Tellask its **tellasker** to request clarification during task execution. In TYPE A, the tellasker is the direct askerDialog. This allows the sideDialog to ask questions and receive guidance while maintaining its own context and progress.

### Tellasker / Tellaskee (Tellask roles)

A **tellasker** is the dialog that issued the current Tellask. A **tellaskee** is the dialog handling that Tellask (this dialog). These are **Tellask roles**, not hierarchy:

- For TYPE A (`tellaskBack`), the tellasker is the direct askerDialog.
- For TYPE B/C, the tellasker may be a different dialog (main dialog or another Side Dialog).
- Responses route to the **current tellasker** recorded in `assignmentFromAsker`.

### Main Dialog

The **main dialog** is the top-level dialog with no askerDialog relationship. It serves as the main entry point for task execution and can spawn multiple levels of sideDialogs.

### Q4H (Questions for Human)

A **Q4H** is a pending question raised by a dialog (main or sideDialog) that requires human input to proceed. Q4Hs are indexed in the dialog's `q4h.yaml` file (an index, not source of truth) and are **cleared by `clear_mind` operations**. The actual question content is stored in the dialog's messages where the `askHuman({ tellaskContent: "..." })` Tellask was recorded.

### SideDialog Index (subdlg.yaml)

A **subdlg.yaml** file indexes pending background callee dialogs for an askerDialog. Like `q4h.yaml`, it is an index file, not the source of truth:

- The index tracks which sideDialog IDs still owe a response to the caller
- Actual sideDialog state is verified from disk (done/ directory)
- Used by the backend coroutine for crash recovery, observability, and result-arrival handling

### SideDialog Registry

The **sideDialog registry** is a main-dialog-scoped Map that maintains persistent references to registered sideDialogs. The registry uses `agentId!sessionSlug` as its key format. It moves with the main dialog to `done/` when the main dialog completes, and is rebuilt on main dialog load by scanning done/ sideDialog YAMLs.
If a Side Dialog is declared dead, its Type B registry entry is removed so the same `agentId!sessionSlug` can start a brand-new Side Dialog on the next Tellask.

### Teammate Tellask

A **teammate Tellask** is a Dominds specific syntax that triggers communication with another agent as sideDialog. Teammate Tellasks have three distinct patterns with different semantics (see Section 3).

**Tellask block structure** (see also [`dominds-terminology.md`](./dominds-terminology.md)):

- **Tellask headline**: the first line `tellaskSessionless({ targetAgentId: "<name>", tellaskContent: "..." })` (additional `tellask* function call` lines in the same block are appended to the headline).
- **Tellask body**: `tellaskContent` payload carried by tellask-special function arguments.
- Structured directives like `sessionSlug` MUST be in the headline.

---

## Backend-Driven Architecture

### Core Design Principle

Dialog driving is a **sole backend algorithm**. The frontend/client never drives dialogs. All dialog state transitions, resumption logic, and generation loops execute entirely in backend coroutines. Frontend only subscribes to publish channels (PubChan) for real-time UI updates.

### Registry Structure

The system maintains three levels of registries for dialog management:

**Global Registry (Server-Scoped)**
A server-wide mapping of `rootId → MainDialog` objects. This is the single source of truth for all active main dialogs. Backend coroutines scan this registry to find dialogs needing driving.

**Local Registry (Per MainDialog)**
A per-root mapping of `selfId → Dialog` objects. This registry contains the main dialog itself plus all loaded sideDialogs, enabling O(1) lookup of any dialog within a main dialog tree.

**SideDialog Registry (Per MainDialog)**
A per-root mapping of `agentId!sessionSlug → SideDialog` objects. This registry tracks TYPE B registered sideDialogs for resumption across multiple interactions. TYPE C transient sideDialogs are never registered.

### Per-Dialog Mutex

Each Dialog object carries an exclusive mutex with an associated wait queue. When a backend coroutine needs to drive a dialog, it first acquires the mutex. If the dialog is already locked, the coroutine enqueues its promise and waits until the mutex is released. This ensures only one coroutine drives a dialog at any moment, preventing race conditions and ensuring consistent state.

### Backend Coroutine Driving Loop

Backend coroutines drive dialogs using the following pattern:

1. Scan the Global Registry to identify main dialogs needing driving
2. For each candidate, check concrete drive sources (Q4H answered, queued prompt, result arrival, open generation recovery, etc.)
3. Acquire the dialog's mutex before driving
4. Execute the generation loop until it naturally idles, reaches Q4H, or completes
5. Release the mutex
6. Persist all state changes to storage

The driving loop continues until a dialog naturally idles, reaches Q4H, or completes. Pending sideDialogs are background callee work; they do not suspend the caller by themselves. However, when the only remaining fact is a pending active callee dispatch and there is no other concrete drive source, the caller should naturally idle rather than keep the mainline spinning through Diligence Push. When a user answers Q4H or a callee result arrives, the backend records that concrete fact and drives the affected dialog only from that fact.

### Frontend Integration

Frontend clients never drive dialogs. Instead, they:

- Subscribe to the current dialog's PubChan for real-time updates
- Receive events for messages, state changes, and UI indicators
- Send user input via API endpoints (drive_dlg_by_user_msg, drive_dialog_by_user_answer)
- Never maintain a full cached dialog corpus in frontend memory: keep only render-scope view data; fetch non-rendered nodes on demand, and drop collapsed subtrees so re-expand always refetches from backend

All driving logic, resumption decisions, and state management remain purely backend concerns.

### Global Dialog Event Broadcaster

Some dialog events are rtws-global rather than dialog-scoped, including `new_q4h_asked`, `q4h_answered`, `sideDialog_created_evt`, and `dlg_touched_evt`.

These events require a **global dialog event broadcaster** to be installed during runtime bootstrap before any dialog-driving logic runs. This broadcaster is mandatory infrastructure, not an optional optimization:

- WebUI server runtime installs a WebSocket fanout broadcaster
- Script / test / future runtimes must also install a broadcaster, typically a recording broadcaster
- Tests should bootstrap the broadcaster at runtime entry and then either assert on captured events or ignore them

Missing broadcaster is therefore a runtime bootstrap invariant violation, not a Q4H/business-layer condition.

### State Persistence

Dialog state is persisted to storage at key points:

- After each message generation
- When Q4H is raised, a background callee dialog is created, or another observable fact changes
- When Q4H is answered or a callee result arrives
- On completion

This ensures crash recovery and enables the backend to resume from any persisted state without depending on frontend state.

### User Interjection And A2H Semantics

When a dialog still carries an inter-dialog reply obligation, but the user temporarily interjects and asks it to handle a local question first, the system must distinguish between the **UI projection** and the **true driving source state**.

Plainly: the system should answer the user's interjection first. Once the user receives a visible answer, the backend records that answer as A2H (Answer to Human) in Human Attention so the user can find and acknowledge it even if the dialog immediately continues automatically. In addition to recovering A2H from visible `saying`, the LLM may also produce structured `answering` output (or call the equivalent `answerHuman` tool entry) to create A2H directly.

**Normative semantics**:

1. Every user interjection message is driven as a complete normal round.
2. If that round needs tools, the system MUST finish the full tool round and any post-tool follow-up before treating the interjection as answered.
3. A visible assistant `saying` settles the pending user-interjection reply only when no same-round function/tellask call remains after that `saying`.
4. The model may also produce structured `answering` output (or call `answerHuman({ answerContent })`) to express "this is an answer for the human." That output is always appended to the dialog's `a2h.yaml`, but it is not the same thing as a Side Dialog's formal reply to its requester.
5. If an inter-dialog reply obligation still exists after the interjection is answered, it remains ordinary durable reply-obligation state. Subsequent continuation is driven by the normal business paths: queued prompts, reply reminders, diligence push, or explicit resume when the dialog is genuinely blocked.
6. A2H disappears when the user acknowledges it. This is intentionally "read then burn"; `answerRef` only links back to the course/genseq that produced the answer. When A2H comes from visible `saying`, the canonical text remains in the transcript; when it comes from structured `answering`, the A2H item itself carries that one-way output text.
7. The Human Attention panel shows Q4H and A2H together. Q4H waits for a human answer; A2H waits only for human acknowledgement.

**Strict boundary**: a formal `askHuman` answer is not part of this "user interjection" category. As soon as a prompt carries a real `q4hAnswerCallId`, it belongs to the askHuman reply channel and semantically continues an already-materialized question/answer chain; it must never be downgraded into temporary local side-chat.

**Modeling boundary**: Dominds does not structurally model "current human question context", does not maintain coordinates for "which human question this A2H answers", and does not store `userInterjection` coordinates in A2H. `a2h` / `answering` is only one-way structured modeling at the LLM output layer: the model produced text meant for the human, and the runtime put it into the human-acknowledgement queue. Questions, interjections, task obligations, and continuation duties remain represented by existing business facts such as prompts, Q4H, reply obligations, and queued prompts.

**Key point**: pending user-interjection reply and inter-dialog reply obligation are independent business facts. Reminder/footer copy can use those two facts directly: if the interjection is still pending, prioritize answering the user; if it is settled and the reply obligation remains active, continue toward the required inter-dialog closure.

**Mental-model warning**:

- Do not flatten every `origin === 'user'` prompt into "interjection"; a non-empty `q4hAnswerCallId` means askHuman answer continuation and follows a different semantic path.
- Do not treat A2H as Q4H. A2H does not block drive and does not route input to an agent.
- Do not treat A2H as a "human question context" database. A2H only carries answer text, acknowledgement state, and answer provenance.
- Do not store A2H in the Problems panel. It belongs to Human Attention and is removed by Ack.

You need all of the following together to understand the behavior correctly:

- reply-guidance suppression for interjection turns
- pending user-interjection reply settlement after visible final `saying`
- A2H persistence and Ack flow
- ordinary continuation through active reply obligations, diligence push, and queued prompts after the user-visible answer

This is an intentionally cross-module semantic contract. Do not locally "simplify" one piece based only on its surface meaning.

---

## 3-Type Teammate Tellask Taxonomy

This section documents the three distinct types of teammate Tellasks in the Dominds system, their syntax, behaviors, and use cases.

```mermaid
flowchart TD
  M["LLM emits tellaskSessionless(...)"] --> Q{"Is this a sideDialog Tellasking its direct askerDialog (tellasker for TYPE A)?"}
  Q -- yes --> A["TYPE A: TellaskBack<br/>(TellaskBack)<br/>Primary: tellaskBack(...) (NO sessionSlug)"]
  Q -- no --> T{Is sessionSlug present?}
  T -- yes --> B["TYPE B: Registered sideDialog Tellask<br/>(Tellask Session / Registered Session Tellask)<br/>tellask(..., sessionSlug=...)"]
  T -- no --> C["TYPE C: Transient sideDialog Tellask<br/>(Fresh Tellask / One-shot Tellask)<br/>tellaskSessionless(...)"]
```

### TYPE A: TellaskBack (Type A / `TellaskBack`)

**Primary syntax**: `tellaskBack({ tellaskContent: "..." })` (NO `sessionSlug`) — `tellaskBack({ tellaskContent: "..." }) sessionSlug ...` is a **syntax error**

**Behavior**:

1. Current callee records the ask-back and waits for the tellasker's answer
2. Driver switches to drive the **tellasker** (direct askerDialog for TYPE A; uses `sideDialog.askerDialog` reference)
3. Tellasker response flows back to the sideDialog
4. Callee continues with the tellasker's response in context

**Key Characteristics**:

- Uses `sideDialog.askerDialog` reference (no registry lookup)
- No registration - askerDialog relationship is inherent
- TYPE A always targets the direct askerDialog (the tellasker for that Tellask).
- `tellaskBack({ tellaskContent: "..." })` is the canonical Type A syntax: it always routes to the tellasker (the dialog that issued the current Tellask).

**Side Dialog delivery rule (normative)**:

- If a Side Dialog has completed all assigned goals and can deliver the final result, it MUST reply directly with the response body; do not use `tellaskBack` to send final delivery.
- Runtime treats that direct reply as the completion delivery to the tellasker and injects the work-language marker automatically (`【Completed】` in English work language, `【最终完成】` in Chinese work language).
- There is no separate technical state for keeping Side Dialogs active. A Side Dialog continues because normal business state still requires work: a tool result needs reaction, Q4H is pending, a downstream callee reply is pending, or an explicit runtime/user prompt is queued. Do not add a side path whose only purpose is to keep a dialog active.
- Reply-obligation tail handling is one small state machine: after runtime reaches a non-suspended tail, it inspects the newest non-empty assistant generation candidate produced by the current drive, preferring public saying over thinking when both exist on the same generation. If a Side Dialog still owes `replyTellask*` and has produced that candidate content without using the exact reply tool, runtime queues one reply-tool reminder. If the next answer to that reminder is still only thinking/saying, runtime intentionally delivers that content through direct-reply fallback and marks it as fallback.
- Runtime must not infer this tail state by scanning historical message text. Historical context is for the model and the UI; scheduling state must be carried explicitly by the current drive result, active reply obligation, pending Q4H indexes, or queued prompts.
- If the work is unfinished, do not default to `tellaskBack`; first use team SOP / role ownership to judge whether a responsible owner is already clear, and if yes for execution work, directly use `tellask` / `tellaskSessionless` for that owner.
- Use `tellaskBack({ tellaskContent: "..." })` only when the tellasker must clarify the request, decide a tradeoff, confirm acceptance criteria, provide missing input, or current SOP cannot determine ownership.
- **FBR exception**: FBR Side Dialogs forbid all tellask calls (including `tellaskBack` / `tellask` / `tellaskSessionless` / `askHuman`); they must list missing context and return.

**Inter-dialog transfer and markers (normative)**:

- Runtime builds a canonical inter-dialog transfer payload for teammate replies; this payload is delivered to target-agent context, and UI must show the same payload verbatim.
- First-line markers are runtime-injected into that transfer payload by semantics; agents must not hand-write them:
  - English work language:
    - Ask-back reply: `【TellaskBack】`
    - Regular completed Side Dialog reply: `【Completed】`
    - FBR Side Dialog reply: `【FBR-Direct Reply】` or `【FBR-Reasoning Only】`
  - Chinese work language:
    - Ask-back reply: `【回问诉请】`
    - Regular completed Side Dialog reply: `【最终完成】`
    - FBR Side Dialog reply: `【FBR-直接回复】` or `【FBR-仅推理】`
- If the tellasker defines a “reply/delivery format” inside the tellask body, keep it to the business delivery structure; do not require tellaskee-side hand-written markers, because runtime injects those markers automatically.
- Source-dialog model raw is naturally preserved in source-dialog persistence; inter-dialog transfer must not rewrite or overwrite that source raw.
- Template-wrapped transfer is allowed: a model output from one dialog may be embedded into a runtime template and sent as the body to another dialog.

**Protocol clarification**:

- When you truly need to ask the tellasker back, emit it via `tellaskBack({ tellaskContent: "..." })`; first judge whether team SOP already identifies another responsible owner. Do not post plain-text intermediate status updates while unfinished.
- A direct plain-text reply is correct when the Side Dialog is already complete and is delivering the final result to the tellasker.

Note: no extra "Status: ..." line is required; the first-line marker is the stage reminder.

**Example**:

```
Current dialog: sub-001 (agentId: "backend-dev")
Tellasker: "orchestrator" (agentId)

LLM emits: tellaskSessionless({ targetAgentId: "orchestrator", tellaskContent: "..." }) How should I handle the database migration?

Result:
- sub-001 records the ask-back and waits for the tellasker's answer
- Driver drives orchestrator with the question
- orchestrator responds with guidance
- sub-001 continues with orchestrator's response
```

### TYPE B: Registered SideDialog Tellask (Type B / `Tellask Session` / Registered Session Tellask)

**Syntax**: `tellask({ targetAgentId: "<anyAgentId>", sessionSlug: "<tellaskSession>", tellaskContent: "..." })` (note the space before `sessionSlug`)

**Fresh Boots Reasoning (FBR) syntax**: `freshBootsReasoning({ tellaskContent: "..." })`

- `freshBootsReasoning` is a dedicated function tool, not a Tellask special-target alias.
- FBR does not accept `sessionSlug` or `mentionList`.
- FBR is driven under a stricter, tool-less policy; see [`fbr.md`](./fbr.md).

**Tellask Session Key Schema**: `<tellaskSession>` uses the same identifier schema as `<mention-id>`:
`[a-zA-Z][a-zA-Z0-9_-]*`. Parsing stops at whitespace or punctuation; any trailing
headline text is ignored for tellaskSession parsing.

**Registry Key**: `agentId!sessionSlug`

**Behavior**:

1. Check registry for existing sideDialog with key `agentId!sessionSlug`
2. **If exists**: Resume the registered sideDialog
3. **If not exists**: Create NEW sideDialog AND register it with key `agentId!sessionSlug`
4. Tellasker records a background callee fact; active callee dispatches do not suspend the tellasker by themselves
5. SideDialog response flows back to the tellasker as a result-arrival fact
6. The tellasker may continue from result arrival, a queued prompt, user input, Diligence Push when no active callee is pending, or another explicit driving source

**Current Tellasker Tracking (important for reuse):**

When a registered sideDialog is Tellasked again (same `agentId!sessionSlug`), the tellasker can be a **different dialog** (main dialog or another Side Dialog). On every Type B Tellask, the sideDialog’s metadata is updated with:

- The **current tellasker ID** (so responses route back to the _latest_ tellasker)
- The **Tellask info** (headline/body, origin role, origin member, callId)

This makes Type B sideDialogs reusable across multiple Tellask sites without losing correct response routing.

**Tellask Context on Resume**:

- On every TYPE B Tellask (new or resumed), the tellasker-provided `mentionList`/`tellaskContent`
  is appended to the sideDialog as a new user message before the sideDialog is driven.
  This ensures the sideDialog receives the latest request context for each Tellask.
- System-injected resume prompts are context only and are **not parsed** for teammate/tool Tellasks.

**Updated Tellask While an Earlier Round Is Still Waiting (normative)**:

- For a registered Side Dialog (`same agentId!sessionSlug`), runtime maintains one current waiting tellasker round.
- If a newer TYPE B Tellask arrives before the earlier round replies, runtime updates pending state and the sideDialog asker stack to the newer round. The earlier call-site still needs a same-`callId` correspondence point, but UI/LLM wording must not claim the target Side Dialog has already received the update until the Side Dialog actually consumes the update prompt.
- The tellaskee is not force-stopped. Instead, its next runtime prompt explains that the work request has been updated, explicitly says not to send a standalone acknowledgement, and includes the latest full assignment.
- Delivery of that updated assignment prompt is queued in-order at the next safe turn boundary. Runtime must not reject the update merely because another normal queued prompt already exists; queued prompts are ordered work, not a single overwrite slot.
- The `registered_assignment_update` up-next item is runtime scheduling state, not a durable redo log. Do not make this queue a new persistence protocol merely to make the update bubble immediately replayable.
- Business self-healing after a backend restart comes from loud facts and sufficient context: pending reminders, asker-stack assignment state, missing old-call terminal facts, and missing assignment anchors should let the LLM decide whether to resend, correct, or wait. It is not a goal to guarantee that every in-memory queued update survives process death.
- A Side Dialog reply produced before that updated assignment prompt is rendered locally MUST NOT be delivered to the tellasker as the newer round's result.

**Key Characteristics**:

- Registry lookup is performed on each Tellask
- Enables **resumption** of previous sideDialogs
- Registered sideDialogs persist in the registry until main dialog completion
- Registry is main-dialog scoped (not accessible to sideDialogs)

**Example**:

```
Main dialog: orchestrator
Registry: {} (empty)

LLM emits: tellask({ targetAgentId: "researcher", sessionSlug: "market-analysis", tellaskContent: "..." })

Result (first call):
- Registry lookup: no "researcher!market-analysis" exists
- Create new sideDialog "researcher!market-analysis"
- Register it in main dialog's registry
- orchestrator records background callee work
- Drive researcher sideDialog
- Response arrives back to orchestrator as a concrete fact

LLM emits again: tellask({ targetAgentId: "researcher", sessionSlug: "market-analysis", tellaskContent: "..." })

Result (second call):
- Registry lookup: "researcher!market-analysis" exists
- Resume existing sideDialog
- orchestrator records background callee work
- Drive existing researcher sideDialog from where it left off
- Response arrives back to orchestrator as a concrete fact
```

### TYPE C: Transient SideDialog Tellask (Type C / `Fresh Tellask` / One-shot Tellask)

**Syntax**: `tellaskSessionless({ targetAgentId: "<nonAskerDialogAgentId>", tellaskContent: "..." })` (NO `sessionSlug`)

**Fresh Boots Reasoning (FBR) self-tellask syntax (default; most common)**: `freshBootsReasoning({ tellaskContent: "..." })`

- `freshBootsReasoning({ tellaskContent: "..." })` targets the current dialog’s agentId and creates a **new ephemeral sideDialog** routed to the same agentId.
- The Side Dialog created by `freshBootsReasoning({ tellaskContent: "..." })` is FBR and is driven under a stricter, tool-less policy; see [`fbr.md`](./fbr.md).
- Use this for most Fresh Boots Reasoning sessions: isolate a single sub-problem, produce an answer, and return.

**Behavior**:

1. Current dialog records a background callee fact; active callee dispatches do not suspend it by themselves
2. Create **NEW sideDialog** with the specified agentId
3. Drive the new sideDialog:
   - For general Type C, the sideDialog is full-fledged (TellaskBack, teammate Tellasks, tools per config).
   - For `freshBootsReasoning({ tellaskContent: "..." })`, runtime applies the FBR tool-less policy (no tools; no Tellasks).
4. SideDialog response flows back to the tellasker as a result-arrival fact
5. The tellasker may continue from result arrival, a queued prompt, user input, Diligence Push when no active callee is pending, or another explicit driving source

**Key Characteristics**:

- **No registry lookup** - always creates a new sideDialog
- **Not registered** - no persistence across Tellasks
- Cannot continue or change the earlier task like Type B can
- Another `tellaskSessionless` is another independent task; it does not update or stop the earlier Type C task
- If later correction, scope change, or earlier wrap-up may be needed, choose Type B `tellask` with `sessionSlug` from the start
- The sideDialog itself is fully capable **except** for `freshBootsReasoning({ tellaskContent: "..." })` FBR, which is tool-less and tellask-free (see `fbr.md`).
- Only difference from TYPE B: no registry lookup/resume capability
- Used for one-off, independent tasks

**Example**:

```
Current dialog: orchestrator

LLM emits: @code-reviewer Please review this PR

Result:
- orchestrator records background callee work
- Create NEW sideDialog with agentId "code-reviewer"
- Drive the code-reviewer sideDialog (it can make its own Tellasks, tools, etc.)
- code-reviewer completes; review findings arrive as a result-arrival fact

LLM emits again: @code-reviewer Review this other PR

Result:
- orchestrator records background callee work
- Create ANOTHER NEW sideDialog (not the same as before!)
- Drive the new code-reviewer sideDialog
- the new review arrives as a result-arrival fact
```

### Comparison Summary

| Aspect                      | TYPE A: AskerDialog Tellask (`TellaskBack`)  | TYPE B: Registered SideDialog Tellask (`Tellask Session`)                                | TYPE C: Transient SideDialog Tellask (`Fresh Tellask`)                                    |
| --------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| **Syntax**                  | `tellaskBack({ tellaskContent: "..." })`     | `tellask({ targetAgentId: "<anyAgentId>", sessionSlug: "<id>", tellaskContent: "..." })` | `tellaskSessionless({ targetAgentId: "<nonAskerDialogAgentId>", tellaskContent: "..." })` |
| **sessionSlug**             | Not allowed                                  | Required                                                                                 | Not allowed                                                                               |
| **Registry Lookup**         | No (uses `sideDialog.askerDialog`)           | Yes (`agentId!sessionSlug`)                                                              | No (never registered)                                                                     |
| **Resumption**              | No (askerDialog not a sideDialog)            | Yes (lookup finds existing)                                                              | No (always new)                                                                           |
| **Registration**            | Not applicable                               | Created AND registered                                                                   | Never registered                                                                          |
| **Tellasker Behavior**      | Callee waits for tellasker reply             | Records background callee fact                                                           | Records background callee fact                                                            |
| **SideDialog Capabilities** | Full (TellaskBack, teammates, tools)         | Full (TellaskBack, teammates, tools)                                                     | Full (TellaskBack, teammates, tools)                                                      |
| **Use Case**                | Clarification from tellasker (`TellaskBack`) | Resume persistent subtask (`Tellask Session`)                                            | One-off independent task (`Fresh Tellask`)                                                |

---

## Core Mechanisms

The Dominds dialog system is built on four interconnected core mechanisms that work together to provide a robust, human-in-the-loop AI collaboration environment:

```mermaid
flowchart TD
  H[Dialog relationship<br/>(main ↔ sideDialogs)] <--> S[SideDialog supply<br/>(responses, pending list, registry)]
  H --> Q["Q4H (askHuman(...))<br/>(q4h.yaml index)"]
  S --> Q

  Q --> UI[Frontend Q4H panel<br/>(questions_count_update)]
  UI --> Ans[User answers Q4H<br/>(drive_dialog_by_user_answer)]
  Ans --> Q

  Clarity[clear_mind] -->|clears| Q
  Clarity -->|preserves| R[Reminders]
  Clarity -->|preserves| Reg[Registry (main only)]
```

### Key Design Principles

1. **Q4H Index in `q4h.yaml`**: Q4H questions are indexed in `q4h.yaml` (as an index, not source of truth) and cleared by mental clarity operations. The actual question content is in the dialog's messages where the `askHuman({ tellaskContent: "..." })` Tellask was recorded. They do not survive `clear_mind`.

2. **Dialog-scoped Q4H**: Any dialog in a Main Dialog / Side Dialog relationship can raise Q4H on its own right. Questions are indexed in the dialog that asked them, not passed upward.

3. **SideDialog Q4H Autonomy**: SideDialogs can ask Q4H questions directly, not as a proxy for tellasker. User navigates to the sideDialog to answer inline.

4. **UI Renders Q4H Like Teammate Tellasks**: The UI treats Q4H similarly to other teammate Tellasks - with navigation linking to the Tellask site in the dialog. The user answers inline using the same input textarea used for regular messages.

5. **SideDialog Response Supply**: SideDialogs write their responses to the _current tellasker’s_ context via persistence (not callbacks). For TYPE B, each Tellask updates the sideDialog’s `assignmentFromAsker` with the latest tellasker + tellaskInfo, so the response is routed to the most recent tellasker (main dialog or sideDialog). This enables detached operation, reuse, and crash recovery.

6. **SideDialog Registry**: Registered sideDialogs (TYPE B Tellasks) are tracked in a main-dialog-scoped registry. The registry persists across `clear_mind` operations and is rebuilt on main dialog load.

7. **State Preservation Contract**:
   - `clear_mind`: Clears messages, clears Q4H index, preserves reminders, preserves registry
   - SideDialog completion: Writes response to the current tellasker, removes from pending list (registry unchanged)
   - SideDialog declared dead: marks runState dead and removes its Type B registry entry; same slug can be reused as a fresh Side Dialog
   - Q4H answer: Clears the answered question from index, continues the dialog

---

## Q4H: Questions for Human

### Overview

Q4H (Questions for Human) is the mechanism by which dialogs can suspend execution and request human input. It is a core, integral mechanism that works seamlessly with sideDialogs, reminders, and mental clarity tools.

### Q4H Data Structure

```typescript
/**
 * HumanQuestion - index entry persisted in q4h.yaml per dialog
 * NOTE: This is an INDEX, not the source of truth. The actual question
 * content is in the dialog's messages where the askHuman() Tellask was recorded
 * (invoked via askHuman({ tellaskContent: "..." })).
 */
interface HumanQuestion {
  readonly id: string; // Unique identifier (UUID) - matches message ID
  readonly mentionList: string; // Question headline/title
  readonly tellaskContent: string; // Detailed question context
  readonly askedAt: string; // ISO timestamp
}
```

**Storage Location**: `<dialog-path>/q4h.yaml` - serves as an index for quick lookup

**Source of Truth**: The actual `askHuman({ tellaskContent: "..." })` Tellask is stored in the dialog's messages (course JSONL files), where the question was asked.

### Q4H Mechanism Flow

```mermaid
sequenceDiagram
  participant D as Dialog (main dialog or sideDialog)
  participant P as Persistence (q4h.yaml)
  participant UI as Frontend UI
  participant WS as WebSocket handler
  participant Driver as driveDialogStream

  D->>P: append HumanQuestion entry to q4h.yaml (index)
  D-->>UI: questions_count_update
  Note over D: dialog becomes non-driveable until answered

  UI->>WS: drive_dialog_by_user_answer(questionId, content)
  WS->>P: remove question from q4h.yaml (delete file if empty)
  WS->>Driver: driveDialogStream(dialog, human answer)
  Driver-->>D: dialog resumes generation
```

### When Does a Dialog Raise Q4H?

Q4H is raised when the `askHuman({ tellaskContent: "..." })` tellask function is invoked by ANY dialog (main dialog or sideDialog) on its own right:

```typescript
// From main/llm/kernel-driver/tellask-special.ts
const isQ4H = callName === 'askHuman';
```

**Invocation Pattern**:

```typescript
askHuman({ tellaskContent: '<question headline>\n<question body content>' });
```

### Q4H Recording Process

```typescript
// When askHuman({ tellaskContent: "..." }) is detected as a teammate Tellask
async function recordQuestionForHuman(
  dlg: Dialog,
  mentionList: string,
  tellaskContent: string,
): Promise<void> {
  const question: HumanQuestion = {
    id: generateDialogID(),
    mentionList,
    tellaskContent,
    askedAt: formatUnifiedTimestamp(new Date()),
  };

  // Load existing questions
  const existing = await DialogPersistence.loadQuestions4HumanState(dlg.id);

  // Append new question
  await DialogPersistence._saveQuestions4HumanState(dlg.id, [...existing, question]);

  // Emit event for UI notification
  await dlg.updateQuestions4Human([...existing, question]);
}
```

### How UI Knows About Q4H

**Event-Based Notification**:

When a question is recorded, the system emits a `questions_count_update` event:

```typescript
// From main/persistence.ts, DiskFileDialogStore.updateQuestions4Human
const questionsCountUpdateEvt: QuestionsCountUpdateEvent = {
  type: 'questions_count_update',
  previousCount: existing.length,
  questionCount: questions.length,
  dialog: {
    selfId: dialog.id.selfId,
    rootId: dialog.id.rootId,
  },
  course: dialog.currentCourse,
};
postDialogEvent(dialog, questionsCountUpdateEvt);
```

**Frontend Response**:

1. Receives `questions_count_update` event
2. Reads `q4h.yaml` to get question index entries
3. Displays Q4H indicator/badge on dialog
4. Questions link to their Tellask sites in the dialog
5. User clicks link to navigate to Tellask site, answers inline

### How User Answers Q4H (Agent-Pull Model)

**Wire Protocol**: `drive_dialog_by_user_answer`

When a dialog is suspended due to Q4H, the agent is waiting for human input. The wire protocol uses an "agent-pull" styled packet to trigger resumption:

```typescript
// shared/types/wire.ts
interface DriveDialogByUserAnswerRequest {
  type: 'drive_dialog_by_user_answer';
  dialog: DialogIdent;
  content: string; // User's answer text
  msgId: string; // Unique ID for tracking
  questionId: string; // ID from q4h.yaml being answered
  continuationType: 'answer';
}
```

**Process (Agent-Pull Model)**:

1. User sees Q4H indicator/badge in UI
2. User clicks Q4H in panel/list, navigates to the `askHuman()` Tellask site
3. User types answer in the input textarea (same as regular messages)
4. Frontend sends `drive_dialog_by_user_answer` packet
5. Backend validates `questionId` against q4h.yaml
6. Backend clears the answered Q4H from q4h.yaml index
7. Backend invokes `driveDialogStream()` with human response as prompt
8. Agent resumes generation with new context (agent-pull satisfied)

**Key Design Points**:

- Uses dedicated packet type for Q4H answers, distinct from regular user messages
- `questionId` ensures the correct Q4H is cleared and answered
- Backend atomically: clear q4h.yaml → resume dialog
- Agent-pull: agent waits for this specific packet before continuing

**Comparison with Regular Messages**:

| Aspect            | Regular Message         | Q4H Answer                          |
| ----------------- | ----------------------- | ----------------------------------- |
| Packet Type       | `drive_dlg_by_user_msg` | `drive_dialog_by_user_answer`       |
| questionId        | Not present             | Required                            |
| Backend Action    | Just drive dialog       | Clear q4h.yaml first → drive dialog |
| Continuation Type | N/A                     | `'answer'`                          |

### SideDialog Q4H Handling

**Key Principles**:

1. Q4H is indexed in the dialog that asked it, not passed upward to the askerDialog
2. SideDialogs ask Q4H on their own right (not as proxy for tellasker)
3. User navigates to the sideDialog to answer inline
4. The `q4h.yaml` file is an index, not source of truth

```mermaid
sequenceDiagram
  participant Asker as AskerDialog
  participant Side as SideDialog
  participant UI as Frontend UI
  participant WS as WebSocket handler
  participant Driver as driveDialogStream

  Asker->>Side: creates sideDialog (Type B or C)
  Note over Asker: AskerDialog records background callee work

  Side->>WS: emits askHuman({ tellaskContent: "..." }) question
  WS-->>UI: questions_count_update
  Note over Side: SideDialog cannot proceed until answered

  UI->>WS: drive_dialog_by_user_answer(dialog=sideDialogId, questionId, content)
  WS->>Driver: driveDialogStream(sideDialog, human answer)
  Driver-->>Side: sideDialog resumes
  Side-->>Asker: response supplied (result-arrival fact; clears this pending callee)
```

### Q4H and Mental Clarity Operations

**Critical Design Decision**: Q4H questions are **CLEARED** by `clear_mind` operations.

```mermaid
flowchart LR
  Before["Before clarity<br/>Messages present<br/>Reminders present<br/>Q4H present"] --> Op[clear_mind]
  Op --> After["After clarity<br/>Messages cleared<br/>Reminders preserved<br/>Q4H cleared"]
```

---

## Dialog Relationship & SideDialogs

### Relationship Overview

```mermaid
flowchart TD
  Main[Main dialog] --> S1[SideDialog sub-001]
  Main --> S2[SideDialog sub-002]
  Main --> S3[SideDialog sub-003]

  S1 --> N1[Nested sideDialog sub-001-001]

  Main -.-> Reg["registry.yaml<br/>(main-scoped, Type B only)"]
  Main -.-> QRoot[q4h.yaml (root)]
  S1 -.-> QS1[q4h.yaml (sub-001)]
  N1 -.-> QN1[q4h.yaml (sub-001-001)]
```

**Typical storage (paths are relative to rtws (runtime workspace)):**

- `.dialogs/run/<root-id>/dialog.yaml`
- `.dialogs/run/<root-id>/latest.yaml`
- `.dialogs/run/<root-id>/reminders.json`
- `.dialogs/run/<root-id>/q4h.yaml`
- `.dialogs/run/<root-id>/course-001.jsonl` (and further courses)
- `.dialogs/run/<root-id>/sideDialogs/<sub-id>/dialog.yaml`
- `.dialogs/run/<root-id>/sideDialogs/<sub-id>/q4h.yaml`
- `.dialogs/run/<root-id>/registry.yaml` (main only; Type B registry)

### SideDialog Response Supply Mechanism

**Core Principle**: SideDialogs supply responses to the **current tellasker's** context via persistence, not callbacks (the tellasker is the direct askerDialog for TYPE A; for TYPE B/C it may be a different dialog).

```mermaid
sequenceDiagram
  participant Asker as Tellasker
  participant Driver as Backend driver
  participant Side as SideDialog
  participant Store as Persistence

  Asker->>Driver: create sideDialog (adds to pending list)
  Driver->>Side: drive sideDialog (detached execution)
  Side-->>Store: persist final response
  Driver-->>Asker: supply result-arrival fact + clear pending callee
  opt result arrival creates an explicit drive source
    Driver-->>Asker: upsert result_arrival trigger + wake queue
  end
```

### SideDialog Q4H and Tellasker Result Arrival

When a sideDialog has raised Q4H and is waiting for human input, the tellasker's result-arrival logic must respect that no callee result exists yet:

```typescript
// Tellasker checks callee completion status
async function checkCalleeResultArrival(tellasker: Dialog): Promise<void> {
  const pending = await loadActiveCalleeDispatches(tellasker.id);

  for (const p of pending) {
    // Check if callee has unresolved Q4H
    const sideDialogQ4H = await DialogPersistence.loadQuestions4HumanState(p.sideDialogId);

    if (sideDialogQ4H.length > 0) {
      // Callee is waiting for human input; no result-arrival fact exists yet.
      log.debug(
        `callee ${p.sideDialogId} has ${sideDialogQ4H.length} Q4H, skipping result arrival`,
      );
      continue;
    }

    // Callee has no Q4H, check if it's done
    const isDone = await isSideDialogCompleted(p.sideDialogId);
    if (isDone) {
      // Incorporate response and schedule from this concrete fact.
      await incorporateSideDialogResponse(tellasker, p.sideDialogId);
    }
  }
}
```

---

## Dialog Control Tools

**Implementation**: `clear_mind` delegates to `Dialog.startNewCourse(newCoursePrompt)`, which:

1. Clears all dialog messages
2. Clears all Q4H questions
3. Increments the course counter
4. Updates the dialog's timestamp
5. Queues `newCoursePrompt` into the dialog's next-prompt queue so the driver can start a new coroutine and use it as the **first `role=user` message** in the next dialog course

### `clear_mind`

**Purpose**: Achieve mental clarity by clearing dialog noise while preserving essential context.

**Function tool arguments**:

- `reminder_content?: string` (optional reminder to add before clearing)

Example:

```text
Invoke the function tool `clear_mind` with:
```

**Behavior**:

- Clears all dialog messages in the current dialog
- Preserves all reminders
- **Clears all Q4H questions** (critical!)
- Preserves sideDialog registry (main dialog only)
- Has no effect on askerDialog
- Redirects attention to Taskdoc
- A system-generated new-course prompt is queued and used as the **first `role=user` message** in the new dialog course
- Starts a new dialog course

**Multi-course dialog note**:

- The first course is created naturally when a main dialog or sideDialog is created.
- Later courses are started by the Dialog Responder via `clear_mind`.
- Exception: the system may auto-start a new course as remediation (e.g., context health becomes critical).

**Implementation Notes**:

- Operation is scoped to the current dialog only
- SideDialogs are not affected by tellasker's `clear_mind`
- Taskdoc remains unchanged and accessible
- Reminders provide continuity across the clarity operation

### Taskdoc Mutation Tools

**Purpose**: Mutate the shared Taskdoc content that all dialogs in the dialog tree reference (without starting a new dialog course). Treat the Taskdoc as the task’s **live coordination bulletin board**.

**Tools**:

- `do_mind`: create one new section; fails if the target already exists.
- `mind_more`: append small entries to an existing section; defaults to `progress`.
- `change_mind`: replace one existing section; fails if the target does not exist or `previous_content_hash` does not match the current content hash.
- `never_mind`: delete one existing section.

**Common section arguments**:

- `selector: "goals" | "constraints" | "progress"` for top-level sections; or a valid selector with `category`
- `category?: string`
- `content: string` for `do_mind` / `change_mind`
- `previous_content_hash: string` for `change_mind`

Example:

```text
Invoke `do_mind` to create a missing section, or `change_mind` with the current `content_hash` as `previous_content_hash` to replace an existing section.
```

**Behavior**:

- Updates the rtws (runtime workspace) Taskdoc content (exactly one section file per call in a `*.tsk/` Taskdoc package)
- **Does not change the Taskdoc path.** `dlg.taskDocPath` is immutable for the dialog's entire lifecycle.
- The updated file immediately becomes available to all dialogs referencing it
- **Does not start a new dialog course.** If starting a new dialog course is desired, use `clear_mind` separately.
- Does not clear messages, reminders, Q4H, or registry by itself
- Affects all participant agents (main and sideDialogs) referencing the same Taskdoc
- Use `progress` for key decisions/status/next steps; use `constraints` for hard rules (don’t leave them only in chat/reminders).

**Implementation Notes**:

- Taskdoc mutation tools are only available in main dialogs (not sideDialogs); sideDialogs must ask the Taskdoc maintainer to update the shared Taskdoc and provide the new section, append entries, merged replacement draft, or section deletion request.
- For `*.tsk/` Taskdoc packages, the Taskdoc is encapsulated: general file tools must not read/write/list/delete anything under `*.tsk/`. See [`encapsulated-taskdoc.md`](./encapsulated-taskdoc.md).

---

## Reminder Management

**Tools**: `add_reminder`, `update_reminder`, `delete_reminder`

**Purpose**: Manage dialog-scoped working memory that persists across dialog cleanup.

**Behavior**:

- Scoped to individual dialogs
- **Survive clear_mind operations**
- **Survive Taskdoc mutation operations**
- Provide guidance for refreshed mental focus
- Support structured capture of insights, decisions, and next steps

**Relationship with Q4H**:

- Reminders persist across mental clarity operations
- Q4H is cleared by mental clarity operations
- They serve different purposes:
  - **Reminders**: Self-generated notes for continuity (survive clarity)
  - **Q4H**: External requests requiring human input (cleared by clarity)

---

## SideDialog Registry

### Overview

The **sideDialog registry** is a main-dialog-scoped data structure that maintains persistent references to registered sideDialogs created via TYPE B (Registered SideDialog Tellask / `Tellask Session`) teammate Tellasks.

### Key Characteristics

| Aspect          | Description                                                      |
| --------------- | ---------------------------------------------------------------- |
| **Scope**       | Main dialog only (not accessible to sideDialogs)                 |
| **Key Format**  | `agentId!sessionSlug` (single-level Map)                         |
| **Storage**     | `registry.yaml` in main dialog directory                         |
| **Lifecycle**   | Retained during normal runs; dead sideDialog entries are removed |
| **Persistence** | Moves with root to `done/` when main dialog completes            |
| **Restoration** | Rebuilt on main dialog load by scanning done/ sideDialog YAMLs   |

### Registry Operations

Example `registry.yaml` (conceptual):

```yaml
researcher!market-analysis:
  sideDialogId: uuid-123
  agentId: researcher
  tellaskSession: market-analysis
  createdAt: 2025-12-27T10:00:00Z
  lastAccessed: 2025-12-27T11:30:00Z
```

```mermaid
flowchart TD
  Tellask["TYPE B Tellask: tellask(..., sessionSlug=...)"] --> Key[Compute key: agentId!sessionSlug]
  Key --> Lookup{Registry hit?}
  Lookup -- yes --> Resume[Restore + drive existing sideDialog]
  Lookup -- no --> Create[Create + register + drive new sideDialog]
  Resume --> Supply[Supply response to tellasker]
  Create --> Supply
```

### Class Design: MainDialog vs SideDialog

**Critical Design Principle**: The sideDialog registry is managed exclusively by `MainDialog` and is **not accessible** to `SideDialog` instances.

**Responsibilities:**

- `MainDialog`
  - Owns the TYPE B sideDialog registry (`registry.yaml`)
  - Creates/registers/looks up registered sideDialogs (`agentId!sessionSlug`)
- `SideDialog`
  - Has a `askerDialog` reference (direct askerDialog) and uses it for TYPE A (`tellaskBack({ tellaskContent: "..." })`)
  - Cannot access or mutate the main dialog registry (by design)

**Mutex Semantics**:

- `locked: true` → SideDialog is currently being driven (mutex held)
- `locked: false` → Entry exists but sideDialog is not locked (can resume)
- Registry does NOT track: 'active' | 'completed' | 'suspended' lifecycle states

**Design Principle**: The registry tracks "locked" (being driven) vs "unlocked" (can resume) state. It does NOT track dialog lifecycle states (active/completed/suspended). Those are Dialog concerns, not Registry concerns. A registered sideDialog may be unlocked (not currently being driven) but still exist as a completed or suspended dialog.

### Registry Persistence

**File Location**: `<main-dialog-path>/registry.yaml`

**Format**:

```typescript
interface SideDialogRegistry {
  [key: string]: {
    sideDialogId: string; // UUID of the sideDialog
    agentId: string; // Agent identifier
    tellaskSession: string; // Tellask session key
    createdAt: string; // ISO timestamp
    lastAccessed?: string; // ISO timestamp (updated on each Tellask)
    locked: boolean; // Mutex state - is someone driving this right now?
  };
}
```

**Persistence Behavior**:

1. **On Registration**: New entry added to registry, file saved
2. **On Resume**: `lastAccessed` updated, file saved
3. **On Clear Mind**: Registry preserved (not cleared)
4. **On Main Completion**: Registry moves with root to `done/`
5. **On Main Load**: Registry rebuilt from done/ sideDialog YAMLs

---

## Technical Architecture

### Dialog Class Structure

The complete Dialog class implementation with all methods, properties, and detailed behavior can be found in `dominds/main/dialog.ts`.

**Key Components**:

- **Dialog Relationship Support**: Tellask/response relationships for sideDialog management
- **Memory Management**: Persistent reminders and ephemeral dialog messages
- **Mental Clarity Operations**: `startNewCourse(newCoursePrompt)` method (clears messages, clears Q4H, increments course, queues new course prompt for the next drive)
- **SideDialog Management**: Creation and coordination of specialized subtasks
- **Q4H Management**: `updateQuestions4Human()` method for question tracking
- **Memory Access**: Integration with Taskdocs and team/agent memories
- **Registry Management** (MainDialog only): Registration and lookup of sideDialogs

### Main Dialog Resolution

For sideDialogs needing to communicate with the Main Dialog, see the implementation in `dominds/main/dialog.ts` which provides methods for resolving dialog relationships.

### Persistence Layer

The persistence layer handles:

- **Dialog Storage**: `dominds/main/persistence.ts`
- **Q4H Storage**: `q4h.yaml` per dialog (cleared by clear_mind)
- **Reminder Storage**: `reminders.json` per dialog
- **Event Persistence**: Course-based JSONL files
- **Registry Storage**: `registry.yaml` per main dialog

**Q4H Persistence Methods**:

```typescript
// In persistence.ts
static async _saveQuestions4HumanState(
  dialogId: DialogID,
  questions: HumanQuestion[],
): Promise<void>

static async loadQuestions4HumanState(
  dialogId: DialogID,
): Promise<HumanQuestion[]>

static async clearQuestions4HumanState(
  dialogId: DialogID,
): Promise<void>
```

**Registry Persistence Methods**:

```typescript
// In MainDialog (dialog.ts)
interface RegistryMethods {
  loadRegistry(): Promise<SideDialogRegistry>;
  saveRegistry(registry: SideDialogRegistry): Promise<void>;
  registerSideDialog(key: string, metadata: SideDialogMetadata): void;
  lookupSideDialog(key: string): SideDialogMetadata | undefined;
  getRegistry(): SideDialogRegistry;
}
```

---

## Dialog Management

### Dialog Relationship Management

**Creation**: SideDialogs are created when agents need to delegate specialized tasks or when complex problems require decomposition.

**Context Inheritance**: New sideDialogs automatically receive:

- Reference to the same rtws (runtime workspace) Taskdoc (recommended: `tasks/feature-auth.tsk/`); `dlg.taskDocPath` is fixed at dialog creation and never reassigned
- Tellasker-provided context (mentionList + tellaskContent) explaining their purpose
- Access to shared team memories
- Access to their agent's individual memories

### SideDialog course header (required)

At the start of every sideDialog course, the runtime must prepend a role header to the assignment prompt:

- EN: `You are the Dialog Responder for this dialog; the tellasker is @xxx (current tellasker).`
- Chinese variant: see [the Chinese doc](./dialog-system.zh.md) for the corresponding work-language header.

**FBR special handling**: FBR is a self-sideDialog and must keep a dedicated header to avoid confusion:

- EN (example): `This is an FBR Side Dialog; the tellasker is @xxx (may be the same agent).`
- Chinese variant example: see [the Chinese doc](./dialog-system.zh.md) for the corresponding FBR header example.

**Insertion point**: prefer a single insertion point by updating `formatAssignmentFromAskerDialog()` (covers `dialog.ts`, `tellask-bridge`).
There is no separate frontend twin anymore; [`main/runtime/inter-dialog-format.ts`](../main/runtime/inter-dialog-format.ts) is the authoritative formatter.

**Storage**: All sideDialogs are stored flat under the main dialog's `sideDialogs/` directory, regardless of nesting depth.

**Navigation**: Each sideDialog maintains a reference to its askerDialog, enabling traversal toward the main dialog.

**Registry**: Registered sideDialogs (TYPE B Tellasks) are tracked in the main dialog's registry and persist across restarts.

### Main dialog fork

An entire main dialog tree can be forked at the start of a chosen root generation into a brand-new main dialog. This is used to preserve prior context while re-running the later Main Dialog/Side Dialog path from a historical branch point.

**Entry points**:

- UI shows `Fork dialog` only on generation bubbles of a main dialog (`selfId === rootId`)
- Backend API: `POST /api/dialogs/:rootId/fork`
- Request body: `{ course, genseq, status? }`

**Semantics (required)**:

- The selected generation bubble is **not** copied into the forked main dialog; the fork point means "branch immediately before this generation starts"
- The copy scope is the **entire main dialog tree**, not just one dialog
- A sideDialog is included only if the root had already persisted it as created before the cutoff
- SideDialog transcript retention is bounded by the root-generation anchor, not by the sideDialog's local `genseq`

**Post-fork actions** (returned by backend to UI):

- `draft_user_text`: if the target generation is a user message, prefill that text into the new dialog input and wait for user confirmation
- `restore_pending`: if there was pending Q4H before the cutoff, restore that user-wait state in the new main dialog; active callee dispatches are copied as background callee facts, not blockers
- `auto_continue`: if there is no pending Q4H or other user-wait fact before the cutoff, initialize the new main dialog as `interrupted(system_stop: fork_dialog_continue)` and have UI immediately send `resume_dialog`

**Consistency requirements**:

- Fork must preserve the same Taskdoc reference
- The forked main dialog and all forked sideDialogs are persisted under `running/` with a new rootId
- Frontend must not expose this entry for Side Dialogs; current implementation supports main dialogs only

### Lifecycle Management

**Active State**: Dialogs remain active while agents are working on tasks.

**Completion**: Dialogs transition to completed state when:

- Tasks are finished successfully
- Agents explicitly mark them complete
- AskerDialogs determine subtasks are no longer needed
- The responsible Dialog Responder determines no pending callee result or Q4H is still needed before completion

**Registry on Completion**: When a main dialog completes, its registry moves with it to the `done/` directory and is preserved for potential restoration.

**Cleanup**: Completed dialogs may be archived or cleaned up based on retention policies.

### Communication Patterns

**Tellasker-Bound Communication**: SideDialogs communicate results, questions, and escalations to their **tellasker**.

- **Clarification Requests (TYPE A / `TellaskBack`)**: A sideDialog may Tellask its tellasker to request clarification while working on its subtask. For TYPE A, the tellasker is the direct askerDialog. The tellasker provides guidance, and the sideDialog continues with updated context.
- **Subtask Response**: When a sideDialog produces a final "saying" content block (no pending Q4H), that message is treated as the response to the **current tellasker** recorded in `assignmentFromAsker` (main dialog or another sideDialog). This keeps responses aligned with the most recent Tellask site.
- **Q4H Escalation**: If a sideDialog has Q4H, it suspends. The user can answer via the UI, which triggers continuation of the sideDialog only.
- **Registered SideDialogs (TYPE B / `Tellask Session`)**: A tellasker can resume a previously created registered sideDialog, enabling ongoing task continuation.
- **Transient SideDialogs (TYPE C / `Fresh Tellask`)**: A tellasker can spawn a one-off sideDialog for independent tasks that don't require persistence.

**Side-Bound Communication**: AskerDialogs provide context, objectives, and guidance to sideDialogs.

**Lateral Communication**: Sibling sideDialogs coordinate through their shared askerDialog.

**Broadcast Communication**: Main dialog (main dialog) can communicate changes (like rtws Taskdoc file updates) to all dialogs through the Taskdoc reference.

---

## Memory Management

### Dialog-Scoped Memory

**Dialog Messages**: Ephemeral dialog content that can be cleared for mental clarity.

**Reminders**: Semi-persistent working memory that survives clarity operations.

**Q4H Questions**: Transient questions for human input that are **cleared by mental clarity operations**.

**Tellasker Call Context**: Immutable context explaining why a sideDialog was created.

**SideDialog Registry**: Main-dialog-scoped persistent mapping of registered sideDialogs (survives clarity operations).

### rtws-Persistent Memory

**Team-Shared Memories**: Persistent across the entire project lifecycle, shared by all agents.

**Agent-Individual Memories**: Personal knowledge that persists per agent across all dialogs.

### Memory Synchronization

**Taskdoc Propagation**: Changes to the rtws Taskdoc file are immediately visible to all dialogs that reference it.

**Memory Updates**: Team and agent memories are updated asynchronously and eventually consistent across all dialogs.

**Q4H Persistence**: Q4H questions are persisted when created and cleared atomically when answered or when clear_mind is called.

**Registry Persistence**: Registry is persisted after each modification and restored on main dialog load.

---

## System Integration

### File System Integration

**Dialog Storage**: Each dialog corresponds to a directory structure containing:

- `<dialog-root>/dialog.yaml` — dialog metadata and configuration
- `<dialog-root>/latest.yaml` — current course tracking and status
- `<dialog-root>/reminders.json` — persistent reminder storage
- `<dialog-root>/q4h.yaml` — Q4H index (cleared by clarity tools)
- `<dialog-root>/registry.yaml` — sideDialog registry (main dialogs only)
- `<dialog-root>/course-001.jsonl` (and further courses) — streamed message files
- `<dialog-root>/sideDialogs/<sideDialog-id>/dialog.yaml`
- `<dialog-root>/sideDialogs/<sideDialog-id>/q4h.yaml` — per-sideDialog Q4H index (cleared by clarity)

**Taskdoc Storage**: Taskdocs are rtws artifacts referenced by dialogs through paths. Taskdocs MUST be encapsulated `*.tsk/` Taskdoc packages.

**Memory Storage**: Team and agent memories are stored in dedicated files within the rtws.

**Registry Storage**: The sideDialog registry (`registry.yaml`) is stored in the main dialog directory and moves to `done/` on main dialog completion.

### Streaming Substream Ordering Contract (Thinking / Saying)

Dominds splits LLM output into multiple “substreams” (thinking, saying, plus markdown / function tool call subsegments derived from saying) and delivers them to the UI via WebSocket events.
To make the UI **faithfully reflect the original generation order**, and to ensure ordering bugs are observable and debuggable across the stack, the following contract MUST hold:

- **Arbitrary alternation is allowed**: Within a single generation (`genseq`), thinking and saying may appear in any number of segments, each as `start → chunk* → finish`, alternating over time.
- **No overlap**: At any moment, at most one active substream exists (thinking or saying). A new `start` MUST NOT occur before the prior segment has `finish`ed.
- **UI renders by event arrival order**: The frontend should not reorder DOM nodes to “fix” ordering; it should append sections in event order to represent the true generation trace.
- **Ordering violations must be loud**: On overlap/out-of-order detection (e.g., thinking and saying both active), the backend SHOULD emit `stream_error_evt` and abort the generation so provider / parsing-chain protocol issues surface quickly.

### LLM Provider Message Projection (Role / Turn)

Dominds persists fine-grained message entries (thinking/saying/tool call/tool result, etc.). In contrast, most mainstream LLM provider chat protocols only support `role=user|assistant` (plus limited tool-specific variants).

- **Ideal target**: Provider SDKs/protocols should natively support `role='environment'` (or an equivalent mechanism) for runtime-injected environment/system content (e.g. reminders, transient guides), so we don't have to disguise environment content as user messages.
- **Current reality**: Most providers do not support `role='environment'`. Therefore, when projecting Dominds messages into provider request payloads, Dominds must flatten internal message kinds into provider-supported roles.
  - Runtime/system notices (`environment_msg`) are projected as `role='user'` text blocks.
  - Self-authored transient guides (`transient_guide_msg`) are projected as `role='assistant'` text blocks.
  - Default reminder context wrappers are runtime/system notices: Dominds' built-in and fallback wrappers land on the `user` side with an explicit system notice marker (for example `[System notice]`) and second-person wording to the LLM. Custom reminder owners keep responsibility for the role they emit under their own contract. Regardless of that owner-level role choice, the surrounding reminder block is environment-style runtime context rather than a fresh user instruction or an assistant-authored chat turn. Reminder projections should be inserted before the real dialog messages and wrapped in a paired reminder-block header/footer; the footer must scope the warning to the content between that header and footer, so real user instructions elsewhere in dialog history are not accidentally downgraded.

Additionally, some providers (especially Anthropic-compatible endpoints) enforce stricter validation around **role alternation** and **tool_use/tool_result boundaries**. Dominds' projection layer must assemble internal fine-grained entries into provider-friendly turns (turn assembly), rather than sending a 1:1 mapping of persisted entries.

### CLI Integration

**Dialog Creation**: New dialogs are created through CLI commands with appropriate context.

**Tool Invocation**: Mental clarity tools are invoked through CLI commands or agent actions.

**Status Monitoring**: Dialog status, active callee dispatches, Q4H count, and registered sideDialogs can be inspected through CLI tools.

### Agent Integration

**Autonomous Operation**: Agents can independently create sideDialogs (TYPE B and C), manage reminders, raise Q4H, and trigger clarity operations.

**Context Awareness**: Agents have full access to their dialog context, memories, dialog relationship position, pending Q4H from sideDialogs, and (for main dialogs) the sideDialog registry.

**Teammate Tellask Capability**: Agents can invoke all three types of teammate Tellasks:

- TYPE A / `TellaskBack`: Tellask the tellasker for clarification (direct askerDialog for TYPE A)
- TYPE B / `Tellask Session`: Tellask/resume registered sideDialogs
- TYPE C / `Fresh Tellask`: Spawn transient sideDialogs

**Tool Access**: All mental clarity tools, Q4H capability, and teammate Tellask capability are available to agents for autonomous cognitive management.

### Dialog State Machine

Dominds' runtime does **not** persist a single enum-like “awaiting …” state. Whether a dialog can be
driven is derived from persisted facts:

- Persisted status (API/index): `running | completed | archived`
- Persisted `latest.yaml`: `status`, `nextStep.triggers`, `generating`
- Derived gates: `hasPendingQ4H()`; active callee dispatches are background callee facts, not a drive gate

**Persisted status lifecycle:**

```mermaid
stateDiagram-v2
  [*] --> running
  running --> completed: mark done
  running --> archived: archive
  completed --> archived: archive
```

**Main driver gating (conceptual):**

```mermaid
flowchart TD
  A[status=running] --> B{canDrive?\\n(no pending Q4H)}
  B -- no --> S[Suspended\\n(waiting on Q4H)]
  S -->|Q4H answered| C{nextStep trigger?}
  R[result arrival] --> C
  B -- yes --> C{nextStep trigger?}
  C -- no --> I[Idle\\n(waiting for trigger)]
  C -- yes --> D[Drive loop\\n(generating=true while streaming)]
  D --> E{hasQueuedPrompt?}
  E -- yes --> C
  E -- no --> I
```

### Teammate Tellask State Transitions

These diagrams focus on **control flow** and avoid box-art alignment so they stay readable even when
rendered in different markdown viewers.

#### TYPE A: TellaskBack (`TellaskBack`) (`tellaskBack({ tellaskContent: "..." })`, no `sessionSlug`)

```mermaid
sequenceDiagram
  participant Side as SideDialog
  participant Driver as Backend driver
  participant Asker as Tellasker (direct askerDialog)

  Side->>Driver: emits `tellaskBack({ tellaskContent: "..." })` + question
  Driver->>Asker: drive tellasker to answer
  Asker-->>Driver: response text
  Driver-->>Side: resume sideDialog with response in context
```

#### TYPE B: Registered SideDialog Tellask (`Tellask Session`) (`tellask({ targetAgentId: "agentId", sessionSlug: "tellaskSession", tellaskContent: "..." })`)

```mermaid
sequenceDiagram
  participant Tellasker as Tellasker
  participant Driver as Backend driver
  participant Reg as Main sideDialog registry
  participant Side as Registered sideDialog

  Tellasker->>Driver: emits `tellask({ targetAgentId: "agentId", sessionSlug: "tellaskSession", tellaskContent: "..." })`
  Driver->>Reg: lookup `agentId!sessionSlug`
  alt registry hit
    Reg-->>Driver: existing sideDialog selfId
    opt earlier round still waiting
      Driver->>Side: queue update notice + latest full assignment
      Driver-->>Tellasker: report earlier round superseded and update registered
      Side-->>Driver: later consumes update prompt and writes assignment anchor
      Driver-->>Tellasker: upgrade call-site link to the concrete assignment bubble
    end
    Driver->>Side: restore + drive
  else registry miss
    Reg-->>Driver: none
    Driver->>Side: create + register + drive
  end
  Side-->>Driver: final response
  Driver-->>Tellasker: supply result-arrival fact + clear pending callee
  opt result arrival creates an explicit drive source
    Driver-->>Tellasker: upsert result_arrival trigger + wake queue
  end
```

Call-site correspondence invariant: every `tellask` call-site `callId` must later have a
same-`callId` historical correspondence point in the tellasker's dialog. A normal sideDialog reply,
cross-course carryover, or registered-session replacement notice can close the call; silently removing
or overwriting a pending round cannot.

WebUI external-link invariant: every `/dl/*` URL must be generated at the business call site with the
target semantics written out there; avoid generic deep-link helpers. `/dl/callsite` only means the
originating bubble in the tellasker's dialog: `selfId/course` are tellasker-dialog coordinates, and
`callId` is that dialog's function-call id. SideDialog assignment/update targets are not call sites;
they use `/dl/dialog` for the sideDialog, or `/dl/genseq` once the delivered assignment `genseq` is
known.
When an existing registered sideDialog is reused, the WebUI first links the new call-site bubble to
the sideDialog via `tellask_call_callee_evt`; the later `tellask_call_anchor_evt` upgrades that target
to the delivered sideDialog `genseq`.

#### TYPE C: Transient SideDialog Tellask (`Fresh Tellask`) (`tellaskSessionless({ targetAgentId: "agentId", tellaskContent: "..." })`; `freshBootsReasoning({ tellaskContent: "..." })` is FBR tool-less)

```mermaid
sequenceDiagram
  participant Tellasker as Tellasker
  participant Driver as Backend driver
  participant Side as Transient sideDialog

  Tellasker->>Driver: emits `tellaskSessionless({ targetAgentId: "agentId", tellaskContent: "..." })`
  Driver->>Side: create (NOT registered)
  Driver->>Side: drive
  Side-->>Driver: final response
  Driver-->>Tellasker: supply response (no registry update)
```

### Q4H Lifecycle State

```mermaid
flowchart TD
  A["askHuman(...) Tellask emitted"] --> B[Append HumanQuestion entry to q4h.yaml]
  B --> C[Emit questions_count_update]
  C --> D[UI shows Q4H badge / list]
  D --> E{How is it cleared?}
  E -->|User answers (drive_dialog_by_user_answer)| F[Remove question from q4h.yaml\\n(delete file if empty)]
  E -->|clear_mind| G[Clear q4h.yaml (all questions)]
  F --> H[Dialog may become driveable again]
  G --> H
```

`q4h.yaml` is treated as an index; the source-of-truth “asked question” content lives in the dialog’s
message stream, referenced by `callSiteRef`.

### SideDialog + Q4H Interaction

```mermaid
sequenceDiagram
  participant Asker as AskerDialog
  participant Side as SideDialog
  participant UI as Frontend UI
  participant WS as WebSocket handler
  participant Driver as driveDialogStream

  Asker->>Side: create sideDialog (Type B or C)
  Note over Asker,Side: AskerDialog records background callee work
  Side->>WS: emits askHuman({ tellaskContent: "..." }) question (Q4H)
  WS-->>UI: questions_count_update (global)

  Note over Side: SideDialog cannot proceed until answered

  UI->>WS: drive_dialog_by_user_answer (dialogId=sideDialogId, questionId, content)
  WS->>Side: clear q4h.yaml entry
  WS->>Driver: driveDialogStream(sideDialog, user answer)
  Driver-->>Side: sideDialog resumes and continues
  Side-->>Asker: sideDialog response supplied to tellasker (result-arrival fact; clears this pending callee)

  opt Asker has a result-arrival drive source
    Asker-->>Asker: upsert result_arrival trigger + wake queue (result-arrival handling)
  end
```

---

## Complete Flow Reference

### 1. Main Dialog Raises Q4H

```mermaid
sequenceDiagram
  participant User as User/Agent
  participant Main as Main Dialog
  participant Store as Persistence (q4h.yaml)
  participant UI as Frontend

  User->>Main: askHuman({ tellaskContent: "..." }) question
  Main->>Store: recordQuestionForHuman()
  Main-->>UI: questions_count_update
  Main-->>Main: suspend root drive loop

  User->>UI: select Q4H
  User->>Main: submit answer
  Main->>Store: loadQuestions4HumanState()
  Main->>Store: clearQuestions4HumanState()
  Main-->>Main: driveDialogStream(answer)
```

### 2. SideDialog Raises Q4H, User Answers via Main

```mermaid
sequenceDiagram
  participant User as User
  participant Asker as AskerDialog (main)
  participant Side as SideDialog
  participant Store as Persistence (side/q4h.yaml, side/response.yaml)
  participant UI as Frontend

  Asker->>Side: createSideDialog()
  Side->>Store: recordQuestionForHuman()
  Side-->>UI: questions_count_update (sideDialog)
  Asker-->>Asker: records background callee work; only Q4H is a user-wait blocker

  User->>UI: select sideDialog Q4H
  User->>Asker: drive_dialog_by_user_answer(targetSideDialogId)
  Asker->>Side: handleDriveDialogByUserAnswer(...)
  Side->>Store: loadQuestions4HumanState()
  Side->>Store: clearQuestions4HumanState()
  Side-->>Side: driveDialogStream(answer)
  Side->>Store: write response.yaml
  Side-->>Asker: supply result-arrival fact
```

### 3. Registered SideDialog Tellask (TYPE B / `Tellask Session` / Registered Session Tellask)

```mermaid
sequenceDiagram
  participant Main as Main Dialog
  participant Store as Persistence (registry.yaml + dialogs/)
  participant Side as SideDialog (@researcher sessionSlug market)

  Main->>Store: lookup registry key "researcher!market"
  alt not found
    Main->>Store: create sideDialog + save registry.yaml
    Main->>Side: drive (background callee work)
  else found
    Main->>Store: load sideDialog + update lastAccessed
    Main->>Side: drive (background callee work)
  end

  Side->>Store: write response.yaml
  Side-->>Main: supply result-arrival fact
```

### 4. Clarity Operations Preserve Registry

| State Element | Effect of `clear_mind`                       |
| ------------- | -------------------------------------------- |
| Messages      | Cleared (new course / fresh message context) |
| Q4H           | Cleared                                      |
| Reminders     | Preserved                                    |
| Registry      | Preserved                                    |

Taskdoc mutation tools are not clarity operations; they update Taskdoc content in-place and do not clear messages/Q4H/reminders/registry.

---

## Performance Considerations

### Scalability

**Flat Storage**: SideDialog flat storage prevents deep directory nesting issues.

**Registry Efficiency**: Single-level Map lookup for registered sideDialogs is O(1).

**Memory Efficiency**: Shared memories reduce duplication across dialogs.

**Lazy Loading**: Dialog content is loaded on-demand to minimize memory usage.

### Reliability

**Atomic Operations**: Q4H and registry persistence use atomic write patterns (temp file + rename).

**Backup and Recovery**: Dialog state can be backed up and restored independently. Registry is restored from done/ on load.

**Error Handling**: System loudly reports and quarantines malformed dialog state instead of silently ignoring dialog corruption, missing files, or registry corruption.

### Monitoring

**Performance Metrics**: System tracks dialog creation, completion, registry size, resource usage, and Q4H count.

**Health Checks**: Regular validation of dialog dialog relationship integrity, Q4H persistence, registry consistency, and memory.

**Debugging Support**: Comprehensive logging and inspection tools for troubleshooting teammate Tellasks, registry operations, and Q4H flows.

---

## Summary

The Dominds dialog system provides a robust framework for human-in-the-loop AI collaboration:

### Four Core Mechanisms

| Mechanism               | Purpose                               | Survives Clarity | Cleared By                                    |
| ----------------------- | ------------------------------------- | ---------------- | --------------------------------------------- |
| **Dialog Relationship** | Tellasker/Side Dialog task delegation | N/A              | N/A                                           |
| **Q4H**                 | Human input requests                  | No               | clear_mind                                    |
| **Mental Clarity**      | Context reset tools                   | N/A              | N/A                                           |
| **Reminders**           | Persistent working memory             | Yes              | N/A                                           |
| **SideDialog Registry** | Registered sideDialog tracking        | Yes              | dead-entry prune on `declare_sideDialog_dead` |

### Three Types of Teammate Tellasks

| Type (internal) | User-facing term  | Syntax                                                                              | Registry              | Use Case                   |
| --------------- | ----------------- | ----------------------------------------------------------------------------------- | --------------------- | -------------------------- |
| TYPE A          | `TellaskBack`     | `tellaskBack({ tellaskContent: "..." })`                                            | no registry           | clarification (ask origin) |
| TYPE B          | `Tellask Session` | `tellask({ targetAgentId: "agentId", sessionSlug: "<id>", tellaskContent: "..." })` | `agentId!sessionSlug` | resumable multi-turn work  |
| TYPE C          | `Fresh Tellask`   | `tellaskSessionless({ targetAgentId: "agentId", tellaskContent: "..." })`           | not registered        | one-shot / non-resumable   |

### Class Responsibility

- **MainDialog**: Manages registry, can make all three teammate Tellask types
- **SideDialog**: Has askerDialog reference, can make TYPE A and TYPE C directly; TYPE B routes through the main dialog registry and updates tellasker context on each Tellask

### Persistence Guarantees

- **Q4H**: Persisted, cleared by clarity operations
- **Reminders**: Persisted, survives clarity operations
- **Registry**: Persisted, survives clarity operations, moves to done/ on completion
- **SideDialogs**: Registered sideDialogs persist in registry; transient sideDialogs are not registered
