From 7a02f74eb8f5b8299e9d355cc9e48a02cc6f2561 Mon Sep 17 00:00:00 2001 From: djl11 Date: Mon, 6 Jul 2026 00:43:37 +0100 Subject: [PATCH 01/29] Rebuild docs from scratch with user-perspective Communication section Replace the developer-oriented architecture/basics/guides pages with a new docs structure written for non-technical platform users, starting with a comprehensive Communication section covering every channel (Console chat, Unify Meet, phone, SMS, WhatsApp, email, Slack, Teams, Discord, meetings), channel setup and costs, voice selection, assistant communication behavior, and recordings/transcripts. --- architecture/codeact.mdx | 90 ----------- architecture/dual-brain.mdx | 71 --------- architecture/managers.mdx | 112 ------------- architecture/memory.mdx | 63 -------- architecture/steerable-handles.mdx | 130 --------------- basics/demos.mdx | 76 --------- basics/overview.mdx | 85 ---------- basics/quickstart.mdx | 74 --------- basics/welcome.mdx | 157 ------------------- communication/behavior.mdx | 66 ++++++++ communication/console-chat.mdx | 46 ++++++ communication/discord.mdx | 33 ++++ communication/email.mdx | 67 ++++++++ communication/meetings.mdx | 41 +++++ communication/microsoft-teams.mdx | 35 +++++ communication/overview.mdx | 125 +++++++++++++++ communication/phone-calls.mdx | 67 ++++++++ communication/recordings-and-transcripts.mdx | 28 ++++ communication/setup.mdx | 93 +++++++++++ communication/slack.mdx | 30 ++++ communication/sms.mdx | 33 ++++ communication/unify-meet.mdx | 70 +++++++++ communication/voice.mdx | 37 +++++ communication/whatsapp.mdx | 67 ++++++++ guides/running-tests.mdx | 111 ------------- introduction.mdx | 25 +++ mint.json | 44 +++--- 27 files changed, 883 insertions(+), 993 deletions(-) delete mode 100644 architecture/codeact.mdx delete mode 100644 architecture/dual-brain.mdx delete mode 100644 architecture/managers.mdx delete mode 100644 architecture/memory.mdx delete mode 100644 architecture/steerable-handles.mdx delete mode 100644 basics/demos.mdx delete mode 100644 basics/overview.mdx delete mode 100644 basics/quickstart.mdx delete mode 100644 basics/welcome.mdx create mode 100644 communication/behavior.mdx create mode 100644 communication/console-chat.mdx create mode 100644 communication/discord.mdx create mode 100644 communication/email.mdx create mode 100644 communication/meetings.mdx create mode 100644 communication/microsoft-teams.mdx create mode 100644 communication/overview.mdx create mode 100644 communication/phone-calls.mdx create mode 100644 communication/recordings-and-transcripts.mdx create mode 100644 communication/setup.mdx create mode 100644 communication/slack.mdx create mode 100644 communication/sms.mdx create mode 100644 communication/unify-meet.mdx create mode 100644 communication/voice.mdx create mode 100644 communication/whatsapp.mdx delete mode 100644 guides/running-tests.mdx create mode 100644 introduction.mdx diff --git a/architecture/codeact.mdx b/architecture/codeact.mdx deleted file mode 100644 index db7efb8d8..000000000 --- a/architecture/codeact.mdx +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: "CodeAct" -description: "The Actor writes Python programs over typed primitives, not flat JSON tool calls" ---- - -## The problem with JSON tool menus - -Standard agent frameworks give the LLM a list of JSON-schema tools. Each turn, the model picks one (or a few), calls them, reads the results, and picks the next. For simple tasks this works. For complex tasks that require composing several operations — look up contacts, query knowledge, send communications, schedule follow-ups — it creates problems: - -- **Combinatorial explosion**: 5 sequential tool calls means 5 round-trips where the model re-reads everything each time -- **No variables**: results from step 1 can't be referenced in step 3 except by re-stating them in natural language -- **No control flow**: loops, conditionals, and error handling require the model to simulate them turn-by-turn -- **Context bloat**: each tool call and result gets appended to the conversation, consuming the context window - -## CodeAct: programs as plans - -Unity's Actor doesn't pick from a tool menu. It writes Python: - -```python -contacts = await primitives.contacts.ask( - "Who was involved in the Henderson project?" -) -for contact in contacts: - history = await primitives.knowledge.ask( - f"What was {contact} last working on?" - ) - await primitives.contacts.update( - f"Send {contact} a catch-up email referencing {history}" - ) -``` - -This runs in a sandboxed execution session with the full `primitives.*` API available — the same typed interfaces the rest of the system uses. One program per turn, with variables, loops, and real control flow. - -The term "CodeAct" comes from the [ICML 2024 paper](https://arxiv.org/abs/2402.01030) "Executable Code Actions Elicit Better LLM Agents". Unity's implementation extends the concept with steerable handles: each `await primitives.X.method(...)` call returns a `SteerableToolHandle` that can be steered from the outer loop. - -## The primitives API - -The Actor's sandbox exposes a `primitives` namespace that maps to real manager APIs: - -| Namespace | Maps to | Examples | -|-----------|---------|----------| -| `primitives.contacts` | ContactManager | `.ask("Who is Alice?")`, `.update("Add Alice's phone: ...")` | -| `primitives.knowledge` | KnowledgeManager | `.ask("What's the Q3 revenue?")`, `.update("Record that ...")` | -| `primitives.tasks` | TaskScheduler | `.ask("What's pending?")`, `.update("Create a task to ...")`, `.execute("Run the weekly report")` | -| `primitives.transcripts` | TranscriptManager | `.ask("What did we discuss yesterday?")` | -| `primitives.web` | WebSearcher | `.ask("What's the weather in London?")` | -| `primitives.files` | FileManager | `.ask("Summarize the attached PDF")`, `.parse("Extract tables from ...")` | -| `primitives.secrets` | SecretManager | `.ask("Do I have a Gmail token?")`, `.update("Store this API key")` | -| `primitives.data` | DataManager | Low-level filter, search, reduce, join operations | - -Each of these is a **natural-language interface**. The parameter is plain English, not a structured query. The manager's internal LLM tool loop figures out how to execute it. - -## Returning handles for steering - -When the Actor's code calls a primitive, the result is a `SteerableToolHandle`. The Actor can either: - -- **Await the result** for immediate use in the next line of code -- **Return the handle** as the last expression, handing steering control back to the ConversationManager - -```python -# Option 1: Await (Actor uses the result immediately) -answer = await primitives.contacts.ask("What's Alice's email?") -await primitives.contacts.update(f"Send Alice at {answer} a reminder") - -# Option 2: Return handle (user can steer a long-running operation) -return await primitives.tasks.execute("Generate the quarterly report") -``` - -Option 2 is preferred for long-running operations — it lets the user pause, interject, or redirect the task through the ConversationManager while it's running. - -## How it compares - -### vs. HermesAgent's PTC (Programmatic Tool Calling) - -HermesAgent has `execute_code` which runs a Python script that calls tools via RPC. The goal is similar — batch many tool steps into one LLM turn to reduce context growth. But PTC is primarily an optimization: the script calls generic tools (file operations, shell commands) and only the script's stdout goes into context. - -CodeAct in Unity is architecturally different: the program calls **typed domain primitives** (`primitives.contacts.ask`, `primitives.knowledge.update`) that each spawn their own steerable LLM tool loop. It's not batching generic tool calls — it's composing domain-specific intelligence. - -### vs. LangGraph code nodes - -LangGraph lets you write Python in graph nodes, but those nodes are statically defined at graph construction time. The LLM doesn't write the graph — a human does. CodeAct lets the LLM write the program dynamically in response to each request. - -## Where to start reading - -| File | What's there | -|------|-------------| -| `unity/actor/code_act_actor.py` | The CodeActActor — plan generation, sandbox, execution | -| `unity/actor/environments/state_managers.py` | How primitives are wired into the sandbox | -| `unity/function_manager/primitives/registry.py` | How the typed `Primitives` API surface is assembled | -| `unity/actor/base.py` | Abstract Actor interface | diff --git a/architecture/dual-brain.mdx b/architecture/dual-brain.mdx deleted file mode 100644 index 54c2fb2b6..000000000 --- a/architecture/dual-brain.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: "Dual-Brain Voice" -description: "How the assistant keeps talking while thinking" ---- - -## The problem - -Most agent frameworks go quiet while working. The user sends a request, the agent runs tools for 30 seconds, then responds. During a voice call, this creates awkward silence. - -Real human assistants don't work this way. They acknowledge your request, keep the conversation going, provide progress updates, and weave results in naturally when they arrive. - -## Two brains, one conversation - -Unity splits voice interactions into two processes: - -**Slow brain** — the ConversationManager. Sees the full picture: all conversations across channels, notifications, in-flight actions, memory. Makes deliberate decisions about what to do. Runs in the main process. - -**Fast brain** — a real-time voice agent on LiveKit, running as a separate subprocess. Sub-second latency. Handles the conversation autonomously — listening, responding, managing turn-taking. - -They communicate over IPC. When the slow brain finishes a task or wants to guide the conversation, it sends the fast brain a notification with one of three modes: - -| Mode | Behavior | -|------|----------| -| **SPEAK** | "Say exactly this" — bypasses the fast brain's LLM entirely, directly synthesizing speech | -| **NOTIFY** | "Here's some context, decide what to do with it" — the fast brain's LLM decides how to weave it in | -| **BLOCK** | Nothing — the fast brain continues on its own | - -## How it plays out - -1. User says: "Can you research flights to Tokyo for next week?" -2. Fast brain immediately responds: "Sure, let me look into that for you." -3. Slow brain starts `actor.act("Research flights to Tokyo...")` → returns a steerable handle -4. While the Actor runs (querying web, comparing prices), the fast brain continues the conversation normally -5. Slow brain sends NOTIFY: "Found 3 direct flights, cheapest is ¥85,000 on ANA" -6. Fast brain weaves it in naturally: "I've found a few options — the best deal looks like an ANA direct flight for about 85,000 yen." - -The user never waits in silence. The conversation is alive throughout. - -## Speech urgency - -Not all slow brain outputs are equal. A progress update on a background task can wait for a natural pause in conversation. But if the user asks "what did you find?" and the results just came in, the fast brain needs to respond immediately. - -A speech urgency evaluator runs on each slow brain notification. It can preempt the current fast brain turn if the notification is urgent enough — for example, if the user just asked a question that the notification directly answers. - -## Concurrent actions during voice - -The ConversationManager tracks all running actions: - -``` -┌─ In-Flight Actions ─────────────────────────────────┐ -│ │ -│ [0] research_flights ██████████░░░ In progress │ -│ → ask, interject, stop, pause │ -│ │ -│ [1] draft_summary ████████████░ In progress │ -│ → ask, interject, stop, pause │ -│ │ -└──────────────────────────────────────────────────────┘ -``` - -Each action gets its own dynamically generated steering tools. During a voice call, the user can say "how's the flight search going?" or "stop the summary, I'll do that myself" — and only the targeted action is affected. The slow brain routes the voice input to the correct action's handle. - -## Where to start reading - -| File | What's there | -|------|-------------| -| `unity/conversation_manager/conversation_manager.py` | Dual-brain orchestration, in-flight actions | -| `unity/conversation_manager/domains/brain.py` | Slow brain decision loop | -| `unity/conversation_manager/domains/brain_action_tools.py` | How the brain starts, steers, and tracks concurrent work | -| `unity/conversation_manager/medium_scripts/call.py` | Fast brain (voice agent) implementation | -| `unity/conversation_manager/prompt_builders.py` | Voice agent prompt construction | diff --git a/architecture/managers.mdx b/architecture/managers.mdx deleted file mode 100644 index ff9784bfe..000000000 --- a/architecture/managers.mdx +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: "State Managers" -description: "Distributed domain managers communicating through English-language APIs" ---- - -## Architecture - -Unity's intelligence is distributed across specialized **state managers**, each owning a different aspect of the assistant's cognition: - -``` -CodeActActor (generates Python plans, calls primitives.* APIs) - │ - ▼ -State Managers (each runs its own async LLM tool loop) - │ - ├── ContactManager — people and relationships - ├── KnowledgeManager — domain facts, structured knowledge - ├── TaskScheduler — durable tasks, execution with live handles - ├── TranscriptManager — conversation history and search - ├── GuidanceManager — procedures, SOPs, how-to knowledge - ├── FileManager — file parsing and registry - ├── ImageManager — image storage, vision queries - ├── FunctionManager — user-defined functions - ├── WebSearcher — web research - ├── SecretManager — encrypted secret storage - ├── BlacklistManager — blocked contact details - └── DataManager — low-level data operations -``` - -Each manager exposes a small public surface — typically `ask` (read-only), `update` (mutations), and sometimes `execute` (start durable work). The Actor orchestrates them through code-first plans. - -## English as API - -The defining design choice: managers communicate through **natural-language interfaces**. The public methods take plain `text: str` parameters, not structured queries. - -```python -# ContactManager.ask — the parameter is English, not SQL -await primitives.contacts.ask("Who did we meet at the conference last month?") - -# KnowledgeManager.update — the instruction is English, not a CRUD operation -await primitives.knowledge.update( - "Record that the Henderson project deadline moved to March 15" -) -``` - -Each manager has its own internal LLM tool loop that interprets the English request and orchestrates lower-level tools (database queries, API calls, etc.) to fulfill it. The caller never sees the implementation details. - -This matters because: - -- **Managers are swappable.** The ContactManager could use a SQL database, a vector store, or an external CRM. The caller's code doesn't change. -- **The system is inspectable.** You can read the Actor's plan and understand what it's doing without reading implementation code. -- **Composition is natural.** The Actor can write `for contact in contacts: await primitives.knowledge.ask(f"What was {contact} working on?")` — the English flows through cleanly. - -## Base class contracts - -Every manager has an abstract base class (`base.py`) that defines the public API contract. The docstrings on these abstract methods **are** the API — they're attached to derived classes via `@functools.wraps` and visible to the LLM when the methods are exposed as tools. - -```python -class BaseContactManager(BaseStateManager): - @abstractmethod - async def ask(self, text: str, ...) -> SteerableToolHandle: - """Answer questions about stored contacts. - - text : str - The user's plain-English question - (e.g. "Show me Alice's phone number."). - """ - - @abstractmethod - async def update(self, text: str, ...) -> SteerableToolHandle: - """Execute English instructions that create or change contacts. - - text : str - The user's instruction - (e.g. "Add Bob's email: bob@example.com"). - """ -``` - -The base class docstrings are **implementation-agnostic** — they never reference internal tools, database schemas, or other managers. This creates a clean separation: the public API is stable even as the implementation evolves. - -## Each manager runs its own LLM - -This is important: when the Actor calls `primitives.contacts.ask(...)`, the ContactManager starts **its own** async LLM tool loop. That loop has its own system prompt, its own set of internal tools (search contacts, filter by field, etc.), and its own conversation context. - -This means: -- The Actor's LLM doesn't need to know how contacts are stored -- The ContactManager's LLM is specialized for contact operations -- Each manager can use a different model if needed (fast model for simple lookups, powerful model for complex reasoning) -- The loops are independently steerable via their handles - -## Manager inventory - -| Manager | `ask` | `update` | `execute` | What it owns | -|---------|-------|----------|-----------|-------------| -| **ContactManager** | Who people are, relationships, contact details | Create, edit, delete, merge contacts | — | People | -| **KnowledgeManager** | Domain facts, structured knowledge | Create, change facts; refactor schemas | — | What the assistant knows | -| **TaskScheduler** | Task status, queue state | Create, edit, reorder tasks | Start tasks (returns live handle) | What the assistant needs to do | -| **TranscriptManager** | Conversation history, search, analysis | — | — | What was said | -| **GuidanceManager** | Procedures, SOPs, strategies | Add, edit, delete guidance | — | How to do things | -| **FileManager** | File metadata, parsing, content questions | — | — | Received files | -| **WebSearcher** | Web search, crawl, extract | — | — | The public internet | -| **SecretManager** | Secret metadata (not values) | Store, edit, delete secrets | — | API keys, tokens | -| **MemoryManager** | — | — | — | Offline consolidation (not interactive) | - -## Where to start reading - -| File | What's there | -|------|-------------| -| `unity/contact_manager/base.py` | Example base class contract | -| `unity/common/state_managers.py` | `BaseStateManager` with caller context injection | -| `unity/actor/environments/state_managers.py` | How primitives are assembled | -| `unity/function_manager/primitives/registry.py` | The typed `Primitives` API surface | diff --git a/architecture/memory.mdx b/architecture/memory.mdx deleted file mode 100644 index 1753dea29..000000000 --- a/architecture/memory.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: "Memory" -description: "Structured knowledge consolidation from conversations" ---- - -## Beyond chat history - -Most agent frameworks store memory as freeform text — conversation logs, MEMORY.md files, or unstructured vector embeddings. When you ask "who's Alice?" six months later, the system searches through raw conversation fragments and hopes for the best. - -Unity takes a different approach. Every 50 messages, the **MemoryManager** runs a background extraction pass and pulls out structured data: - -- **Contact profiles** — who people are, their roles, organizations, relationships -- **Per-contact summaries** — what you've been discussing with each person, sentiment, themes -- **Response policies** — how each person prefers to communicate (formal vs casual, preferred channel, timezone) -- **Domain knowledge** — project details, preferences, long-term facts -- **Tasks** — things committed to, deadlines, follow-ups - -This is stored in typed, queryable tables — not freeform text. After a month of use, the system has a working model of your world. - -This is continual learning at the agent layer, not model fine-tuning: Unity improves by updating durable structured state that future reasoning can query directly. - -## How consolidation works - -The MemoryManager observes message events on the **EventBus** and runs extraction when the message count crosses a threshold: - -1. **Transcript chunk** — the MemoryManager grabs the latest unconsolidated messages -2. **Contact extraction** — identifies people mentioned, creates or updates contact records with new details -3. **Knowledge extraction** — pulls out facts, project details, preferences, and stores them in the KnowledgeManager's tables -4. **Task extraction** — identifies commitments, deadlines, and follow-ups -5. **Summary updates** — updates rolling per-contact summaries - -Each extraction step uses its own LLM call with a specialized prompt. The outputs go directly into the respective managers' structured storage — ContactManager for people, KnowledgeManager for facts, TaskScheduler for commitments. - -## Structured vs. freeform - -The distinction matters in practice: - -| Capability | Freeform memory (MEMORY.md) | Structured consolidation (Unity) | -|---|---|---| -| "Who's Alice?" | Search text for "Alice" mentions | Query `Contacts` table: name, role, org, relationship | -| "What did Alice say about the budget?" | Fuzzy search across all history | Join `Contacts` → `Transcripts` with contact filter | -| "How does Alice prefer to communicate?" | Hopefully mentioned somewhere | `response_policy` field on Alice's contact record | -| "What's overdue?" | Parse dates from text (unreliable) | Query `Tasks` with `deadline < now` filter | -| After 10,000 messages | Slow, noisy search | Same speed — structured tables don't degrade | - -## The EventBus connection - -The MemoryManager subscribes to events via the **EventBus** (`unity/events/event_bus.py`). When `enable_callbacks` is set, it registers handlers for message events and `ManagerMethod` events. This means memory consolidation can run asynchronously without blocking the main conversation flow. - -The EventBus itself is a typed pub/sub backbone using Pydantic event envelopes. Events carry lineage labels aligned with the tool loop hierarchy, so the MemoryManager can attribute events to specific operations. - -## Offline by design - -The MemoryManager is explicitly **offline** — it runs periodically, not in the live conversation loop. Its methods return strings (not steerable handles) because there's no user to steer them. This keeps the main conversation path fast: the user talks to the ConversationManager and Actor, while memory consolidation happens in the background. - -## Where to start reading - -| File | What's there | -|------|-------------| -| `unity/memory_manager/memory_manager.py` | The consolidation pipeline | -| `unity/events/event_bus.py` | Typed pub/sub backbone | -| `unity/contact_manager/base.py` | Where contact extractions land | -| `unity/knowledge_manager/base.py` | Where knowledge extractions land | diff --git a/architecture/steerable-handles.mdx b/architecture/steerable-handles.mdx deleted file mode 100644 index 980afe40b..000000000 --- a/architecture/steerable-handles.mdx +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: "Steerable Handles" -description: "The universal protocol for mid-flight control of async agent operations" ---- - -## The problem with flat agent loops - -Most agent frameworks work like this: one LLM, one loop, one tool call at a time. The model picks a tool, calls it, reads the result, picks the next. If you want to interrupt — redirect the task, provide new information, pause for something urgent — you cancel and start over. - -This is fine for short, predictable tasks. It breaks down when: - -- The agent is mid-way through a 30-step research task and you realize you forgot to mention a constraint -- You need the agent to pause its current work, handle something urgent, then resume where it left off -- You want to ask "how's it going?" without disrupting the work in progress -- Multiple operations need to run concurrently with independent steering - -In these cases, "cancel and restart" loses all accumulated context and work. - -## Steerable handles - -Every significant operation in Unity — whether it's searching contacts, updating knowledge, or executing a multi-step task — runs inside its own async LLM tool loop and returns a **steerable handle**. The handle is a live reference to the running operation with a uniform control surface: - -```python -handle = await actor.act("Research flights to Tokyo and draft an itinerary") - -# Twenty seconds later, while it's still working: -await handle.interject("Also check train options from Tokyo to Osaka") - -# Ask about progress without disrupting the work: -status_handle = await handle.ask("What have you found so far?") - -# Pause for something urgent: -await handle.pause() -# ... deal with the urgent thing ... -await handle.resume() - -# Or stop entirely: -await handle.stop("No longer needed") -``` - -## The protocol - -Every handle implements `SteerableToolHandle` — the abstract protocol defined in `unity/common/async_tool_loop.py`: - -| Method | Purpose | -|--------|---------| -| `ask(question)` | Query progress or results without modifying the task. Returns a new handle for the answer. | -| `interject(message)` | Inject new context, corrections, or requirements into the running task. | -| `pause()` | Pause the task. In-flight operations finish, but no new actions start. | -| `resume()` | Resume a paused task. Work that completed during the pause is processed first. | -| `stop(reason)` | Cancel the task and all its sub-operations. | -| `done()` | Check whether the task has completed. | -| `result()` | Await the final output. | - -This is the **minimum universal contract**. Derived handles can extend it — for example, `ActiveTask.stop` adds a `cancel` parameter, and `ConversationManagerHandle.interject` adds `pinned` for priority messages. - -## Nested composition - -The key insight is that handles **compose through arbitrary depth**. When the Actor calls `primitives.contacts.ask(...)`, the ContactManager starts its own tool loop and returns its own handle — nested inside the Actor's handle, which is nested inside the ConversationManager's handle. - -``` -ConversationManager (SteerableToolHandle) - └── Actor.act (SteerableToolHandle) - ├── ContactManager.ask (SteerableToolHandle) - ├── KnowledgeManager.update (SteerableToolHandle) - └── TaskScheduler.execute (SteerableToolHandle) -``` - -Steering propagates correctly through the full depth: - -- **User interjects on the ConversationManager** → the CM decides whether to forward to the Actor → the Actor decides whether to forward to the active manager -- **User pauses the Actor** → all active manager operations continue to their current step, then wait -- **User asks the Actor** → spawns an inspection loop that can query any active sub-handle - -Each level makes its own decisions about how to handle steering events. The ConversationManager might translate a user's casual "also check hotels" into a structured interjection on the Actor's handle. The Actor might route that to the specific sub-operation it's relevant to. - -## How it works under the hood - -### Async tool loops - -Each handle is backed by an `asyncio.Task` running an LLM tool loop (`start_async_tool_loop` in `unity/common/async_tool_loop.py`). The loop: - -1. Builds a prompt with the current context (including any interjections received since the last turn) -2. Calls the LLM -3. Executes the tool calls from the response -4. Repeats until the LLM produces a final response (no more tool calls) - -Interjections are injected between turns via an async queue. Pause/resume uses `asyncio.Event` flags. Stop triggers graceful cancellation of the underlying task. - -### Context separation - -Nested loops use **role rewriting** to prevent confusion. When an outer loop's context is injected into an inner loop (for `ask` or `interject`), the messages are tagged with distinct roles (`outer_user`, `outer_assistant`) so the inner LLM can distinguish between its own conversation and the parent's context. - -### Lineage tracking - -Each loop gets a position in a **lineage hierarchy** tracked via `contextvars`. This is used for: - -- Structured logging (every log line shows its position in the nesting tree) -- Event attribution (the EventBus tags events with their loop lineage) -- Debugging (you can trace exactly which operation at which depth produced a given action) - -The lineage label looks like: `ConversationManager->Actor(a3f2)->ContactManager(7b1c)`. - -### Forward dispatching - -When a parent loop needs to forward a steering call to a child handle whose concrete type it doesn't know, it uses `forward_handle_call`. This introspects the target method's actual signature, filters out kwargs the target doesn't accept, and applies positional fallbacks — no hand-written `try/except TypeError` cascades needed. - -## Why this matters - -### vs. OpenClaw - -OpenClaw's steering is Gateway-mediated: you can inject a message into the current run's queue or abort the run. There's no nested composition — the Gateway manages one run at a time per session, and subagents are separate sessions, not nested steerable loops. - -### vs. HermesAgent - -HermesAgent's `interrupt()` is essentially a kill signal with optional message, propagated to child agents. There's no `pause/resume`, no `ask` for status, no mid-task interjection that modifies the operation in progress. - -### What Unity enables - -The steerable handle pattern means the assistant can hold a real-time voice conversation while executing background tasks, accept corrections mid-flight without losing progress, and manage multiple concurrent operations that the user can independently query, redirect, or cancel. The user doesn't wait in silence — they interact with a working system. - -## Where to start reading - -| File | What's there | -|------|-------------| -| `unity/common/async_tool_loop.py` | `SteerableToolHandle` protocol and `AsyncToolLoopHandle` implementation | -| `unity/common/_async_tool/loop.py` | The async tool loop engine — turn execution, interjection handling, compression | -| `unity/common/_async_tool/messages.py` | `forward_handle_call`, helper tool recognition, context role rewriting | -| `unity/common/_async_tool/loop_config.py` | Lineage tracking via contextvars | -| `unity/common/_async_tool/multi_handle.py` | `MultiHandleCoordinator` for concurrent requests in one loop | diff --git a/basics/demos.mdx b/basics/demos.mdx deleted file mode 100644 index bed53e627..000000000 --- a/basics/demos.mdx +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: "Demos" -description: "Demo videos" ---- - -### Launch Video - -The full product walkthrough: hiring, onboarding, multi-channel collaboration, computer use, and continuous learning. - -