# Technical Documentation: ThinkInAIXYZ/deepchat

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/ThinkInAIXYZ/deepchat)

### Overview (`README.md`)

<p align='center'>
<img src='./build/icon.png' width="150" height="150" alt="DeepChat AI Assistant Icon" />
</p>

<h1 align="center">DeepChat - Open-Source Local-First AI Agent Desktop Client</h1>

<p align="center">DeepChat is an open-source, local-first AI agent desktop client with rich agent capabilities, designed around the Tape.systems philosophy, with support for MCP, Skills, ACP, and remote control integrations for messaging apps.</p>

<p align="center">
  <a href="https://github.com/ThinkInAIXYZ/deepchat/stargazers"><img src="https://img.shields.io/github/stars/ThinkInAIXYZ/deepchat" alt="Stars Badge"/></a>
  <a href="https://github.com/ThinkInAIXYZ/deepchat/network/members"><img src="https://img.shields.io/github/forks/ThinkInAIXYZ/deepchat" alt="Forks Badge"/></a>
  <a href="https://github.com/ThinkInAIXYZ/deepchat/pulls"><img src="https://img.shields.io/github/issues-pr/ThinkInAIXYZ/deepchat" alt="Pull Requests Badge"/></a>
  <a href="https://github.com/ThinkInAIXYZ/deepchat/issues"><img src="https://img.shields.io/github/issues/ThinkInAIXYZ/deepchat" alt="Issues Badge"/></a>
  <a href="https://github.com/ThinkInAIXYZ/deepchat/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ThinkInAIXYZ/deepchat" alt="License Badge"/></a>
  <a href="https://github.com/ThinkInAIXYZ/deepchat/releases/latest"><img src="https://img.shields.io/endpoint?url=https://api.pinstudios.net/api/badges/downloads/ThinkInAIXYZ/deepchat/total" alt="Downloads"></a>
</p>

<div align="center">
  <a href="https://trendshift.io/repositories/15162" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15162" alt="ThinkInAIXYZ%2Fdeepchat | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>

<div align="center">
  <a href="./README.zh.md">中文</a> / <a href="./README.md">English</a> / <a href="./README.jp.md">日本語</a>
</div>

## ❤️ Sponsor

<table>
  <tr>
    <td width="25%" align="center">
      <a href="https://www.openmodel.ai?ref=ZGLyq8YQ">
        <img src="./assets/sponsors/openmodel.png" alt="OpenModel" width="240" />
      </a>
    </td>
    <td width="75%">
      Thanks to OpenModel for sponsoring this project! OpenModel offers client-specific discounts of
      up to 90% for Codex and 60% for Claude Code across supported models, helping developers cut API
      costs without changing how they work. Just connect your OpenModel API key and keep using Codex
      or Claude Code as usual — supported clients are detected automatically and discounted routes
      are applied with no extra parameters or manual routing required. You can also configure
      fallback behavior per API key, choosing whether to continue at standard pricing or stop when a
      discounted route is temporarily unavailable. DeepChat users can register via <a
      href="https://www.openmodel.ai?ref=ZGLyq8YQ">this link</a>.
    </td>
  </tr>
  <tr>
    <td width="25%" align="center">
      <a href="https://www.packyapi.ai/register?aff=0A33">
        <img src="./assets/sponsors/packycode.png" alt="PackyCode" width="240" />
      </a>
    </td>
    <td width="75%">
      PackyCode is a stable, high-performance API relay provider, offering relay services for Claude
      Code, Codex, Gemini, and more. With automatic failover, smart routing, and unlimited
      concurrency, it turns AI into a real productivity tool. Register via <a
      href="https://www.packyapi.ai/register?aff=0A33">this link</a> and get started!
    </td>
  </tr>
</table>

## 📑 Table of Contents

- [📑 Table of Contents](#-table-of-contents)
- [🚀 Project Introduction](#-project-introduction)
- [💡 Why Choose DeepChat](#-why-choose-deepchat)
- [📦 Quick Start](#-quick-start)
  - [Download and Install](#download-and-install)
  - [Configure Models](#configure-models)
  - [Start Conversations](#start-conversations)
- [🔥 Main Features](#-main-features)
- [📼 Tape & Trace](#-tape--trace)
- [🧠 Skills Support](#-skills-support)
- [🧩 ACP Integration (Agent Client Protocol)](#-acp-integration-agent-client-protocol)
- [📡 Remote Control](#-remote-control)
- [🤖 Supported Model Providers](#-supported-model-providers)
  - [Compatible with any model provider in OpenAI/Gemini/Anthropic API format](#compatible-with-any-model-provider-in-openaigeminianthropic-api-format)
- [🔍 Use Cases](#-use-cases)
- [💻 Development Guide](#-development-guide)
  - [Install Dependencies](#install-dependencies)
  - [Start Development](#start-development)
  - [Build](#build)
- [👥 Community \& Contribution](#-community--contribution)
- [⭐ Star History](#-star-history)
- [👨‍💻 Contributors](#-contributors)
- [📃 License](#-license)

## 🚀 Project Introduction

DeepChat is a powerful open-source, local-first AI agent desktop client that brings together models, tools, Skills, agent runtimes, Tape, and long-running sessions in one desktop app. Whether you're using cloud APIs like OpenAI, Gemini, Anthropic, or locally deployed Ollama models, DeepChat delivers a smooth user experience.

DeepChat's sessions and agent processes follow the Tape.systems philosophy: keep the process, so context, tool calls, requests, and results stay recoverable, traceable, and inspectable. It also provides strong MCP support, installable Skills, ACP agent integration, and remote control for Telegram, Feishu/Lark, QQBot, Discord, WeChat iLink, and other messaging workflows.

<table align="center">
  <tr>
    <td align="center" style="padding: 10px;">
      <img src='./assets/screenshots/main-light.png' alt="DeepChat main interface in light mode" width="400"/>
      <br/>
    </td>
    <td align="center" style="padding: 10px;">
      <img src='./assets/screenshots/main-dark.png' alt="DeepChat main interface in dark mode" width="400"/>
      <br/>
    </td>
  </tr>
</table>

## 💡 Why Choose DeepChat

Compared to other AI tools, DeepChat offers the following unique advantages:

- **Local-First Agent Desktop Client**: Run DeepChat agents, ACP agents, and remote-ready bots in one local app
- **Tape.systems Philosophy**: Preserve recoverable session history, trace request context, and inspect token budgets when agent work gets complex
- **Skills That Travel**: Install, import, export, and enable reusable Skills per conversation for code review, documents, frontend work, Office/PDF tasks, and more
- **Native ACP Integration**: Run ACP-compatible coding and task agents as first-class entries in the model selector
- **Strong MCP Support**: Support Resources, Prompts, Tools, multiple transports, inMemory services, and one-click installation
- **Remote-Ready Workflows**: Control DeepChat sessions from Telegram, Feishu/Lark, QQBot, Discord, and WeChat iLink
- **Unified Multi-Model Management**: One application supports mainstream cloud LLMs and local Ollama models, eliminating the need to switch between multiple apps
- **Privacy-Focused**: Local data storage and network proxy support reduce the risk of information leakage
- **Business-Friendly**: Embraces open source under the Apache License 2.0, suitable for both commercial and personal use

## 📦 Quick Start

### Download and Install

You can install DeepChat using one of the following methods:

**Option 1: GitHub Releases**

Download the latest version for your system from the [GitHub Releases](https://github.com/ThinkInAIXYZ/deepchat/releases) page:

- Windows: `.exe` installation file
- macOS: `.dmg` installation file
- Linux: `.AppImage` or `.deb` installation file

**Option 2: Official Website**

Download from the [official website](https://deepchatai.cn/#/download).

**Option 3: Homebrew (macOS only)**

For macOS users, you can install DeepChat using Homebrew:

```bash
brew install --cask deepchat
```

### Configure Models

1. Launch the DeepChat application
2. Click the settings icon
3. Select the "Model Providers" tab
4. Add your API keys or configure local Ollama

### Start Conversations

1. Click the "+" button to create a new conversation
2. Select the model you want to use
3. Start communicating with your AI assistant

## 🔥 Main Features

- 🤖 **Local-First Agent Desktop Client**
  - Select DeepChat, ACP, and remote-capable agents from one model-like entry point
  - Run long-lived sessions with project folders, permission modes, tool output, and resumable context
- 📼 **Tape & Trace**
  - Session Tape records structured work history for recovery, resume, and future agent memory flows
  - Trace previews show request sequences, provider/model metadata, Tape view manifests, included entries, and token budgets
- 🧠 **Skills**
  - Install Skills from folders, ZIP files, or URLs
  - Enable Skills per conversation so DeepChat can load task-specific instructions, references, and optional scripts
  - Import and export Skills with Claude Code, Codex, Cursor, Windsurf, GitHub Copilot, and other compatible tools
- 🤝 **ACP (Agent Client Protocol) Agent Integration**
  - Run ACP-compatible agents (built-in or custom commands) as selectable “models”
  - ACP workspace UI for structured plans, tool calls, and terminal output when provided by the agent
- 📡 **Remote Control**
  - Control DeepChat sessions from Telegram, Feishu/Lark, QQBot, Discord, and WeChat iLink
  - Bind remote endpoints to sessions, switch models, answer pending interactions, stop runs, and open desktop sessions remotely
- 🌐 **Multiple Cloud LLM Provider Support**: DeepSeek, OpenAI, Moonshot/Kimi, Grok, Gemini, Anthropic, and more
- 🏠 **Local Model Deployment Support**:
  - Integrated Ollama with comprehensive management capabilities
  - Control and manage Ollama model downloads, deployments, and runs without command-line operations
- 🚀 **Rich and Easy-to-Use Chat Capabilities**
  - Complete Markdown rendering with code block rendering based on industry-leading [CodeMirror](https://codemirror.net/)
  - Multi-window + multi-tab architecture supporting parallel multi-session operations across all dimensions, use large models like using a browser, non-blocking experience brings excellent efficiency
  - Supports Artifacts rendering for diverse result presentation
  - Messages support retry to generate multiple variations; conversations can be forked freely, ensuring there's always a suitable line of thought
  - Supports rendering images, Mermaid diagrams, and other multi-modal content; supports GPT-4o, Gemini, Grok text-to-image capabilities
  - Supports highlighting external information sources like search results within the content
- 🔍 **Robust Search Extension Capabilities**
  - Built-in integration with leading search APIs like Bocha Search and Brave Search, allowing the model to intelligently decide when to search
  - Supports mainstream search engines like Google, Bing, Baidu, and Sogou Official Accounts search by simulating user web browsing, enabling the LLM to read search engines like a human
  - Supports reading any search engine; simply configure a search assistant model to connect various search sources, whether internal networks, API-less engines, or vertical domain search engines, as information sources for the model
- 🔧 **Strong MCP (Model Context Protocol) Support**
  - Full support for Resources / Prompts / Tools
  - Supports StreamableHTTP, SSE, Stdio, and other transports
  - Official Node.js toolchain for npx/node-style services, installed from Settings when needed
  - inMemory services for code execution, web information retrieval, file operations, and other common utilities
  - Clear tool-call display with parameter and return-data debugging
  - DeepLink support for one-click MCP service installation
- 💻 **Multi-Platform Support**: Windows, macOS, Linux
- 🎨 **Beautiful and User-Friendly Interface**, user-oriented design, meticulously themed light and dark modes
- 🔗 **Rich DeepLink Support**: Initiate conversations via links for seamless integration with other applications, including one-click MCP service installation.
- 🚑 **Security-First Design**: Chat data and configuration data have reserved encryption interfaces and code obfuscation capabilities
- 🛡️ **Privacy Protection**: Supports screen projection hiding, network proxies, and other privacy protection methods to reduce the risk of information leakage
- 💰 **Business-Friendly**:
  - Embraces open source, based on the Apache License 2.0 protocol, enterprise use without worry
  - Enterprise integration requires only minimal configuration code changes to use reserved encrypted obfuscation security capabilities
  - Clear code structure, both model providers and MCP services are highly decoupled, can be freely customized with minimal cost
  - Reasonable architecture, data interaction and UI behavior separation, fully utilizing Electron's capabilities, rejecting simple web wrappers, excellent performance

## 📼 Tape & Trace

DeepChat's session Tape follows the Tape.systems philosophy and keeps agent work recoverable and inspectable. Trace previews expose request sequences, provider/model metadata, Tape view manifests, included or excluded entries, and token budgets, making long-running agent sessions easier to debug and resume.

## 🧠 Skills Support

DeepChat Skills are designed to be compatible with the standard Agent Skills specification. A Skill can include task instructions, reference files, assets, and optional scripts, so DeepChat can act more like a domain specialist after it is enabled.

You can install Skills from folders, ZIP files, or URLs, and import/export them with Claude Code, Codex, Cursor, Windsurf, GitHub Copilot, Kiro, Antigravity, OpenCode, Goose, Kilo Code, and other compatible tools.

Built-in Skills cover algorithmic art, code review, DeepChat CLI, DeepChat settings, document collaboration, DOCX, frontend design, git commit messages, infographic syntax, MCP building, memory management, PDF, PPTX, Skill creation, and XLSX workflows.

Quick start:

1. Open **Settings → Skills**
2. Install or import a Skill
3. Enable it in conversations that need that capability

## 🧩 ACP Integration (Agent Client Protocol)

DeepChat has built-in support for [Agent Client Protocol (ACP)](https://agentclientprotocol.com), allowing you to integrate external agent runtimes into DeepChat with a native UI. Once enabled, ACP agents appear as first-class entries in the model selector, so you can use coding agents and task agents directly inside DeepChat.

Quick start:

1. Open **Settings → ACP Agents** and enable ACP
2. Enable a built-in ACP agent or add a custom ACP-compatible command
3. Select the ACP agent in the model selector to start an agent session

To explore the ecosystem of compatible agents and clients, see: https://agentclientprotocol.com/overview/clients

## 📡 Remote Control

DeepChat can be controlled from messaging apps, so you can keep a session running even when you are away from the desktop. Configure remote channels under **Settings → Remote**.

Supported channels include Telegram, Feishu/Lark, QQBot, Discord, and WeChat iLink. Remote endpoints can bind to one DeepChat session, then create new sessions, list and switch recent sessions, stop generation, open the current session on desktop, answer pending questions or permission prompts, switch models, and check runtime status.

Common commands include `/start`, `/help`, `/pair`, `/new`, `/sessions`, `/use`, `/stop`, `/open`, `/pending`, `/model`, and `/status`.

## 🤖 Supported Model Providers

<table>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/deepseek-color.svg" width="50" height="50" alt="Deepseek Icon"><br/>
      <a href="https://deepseek.com/">Deepseek</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/moonshot.svg" width="50" height="50" alt="Moonshot Icon"><br/>
      <a href="https://moonshot.ai/">Moonshot</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/openai.svg" width="50" height="50" alt="OpenAI Icon"><br/>
      <a href="https://openai.com/">OpenAI</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/gemini-color.svg" width="50" height="50" alt="Gemini Icon"><br/>
      <a href="https://gemini.google.com/">Gemini</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/ollama.svg" width="50" height="50" alt="Ollama Icon"><br/>
      <a href="https://ollama.com/">Ollama</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/qiniu.svg" width="50" height="50" alt="Qiniu Icon"><br/>
      <a href="https://www.qiniu.com">Qiniu</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/newapi.svg" width="50" height="50" alt="New API Icon"><br/>
      <a href="https://www.newapi.ai/">New API</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/grok.svg" width="50" height="50" alt="Grok Icon"><br/>
      <a href="https://x.ai/">Grok</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/zhipu-color.svg" width="50" height="50" alt="Zhipu Icon"><br/>
      <a href="https://open.bigmodel.cn/">Zhipu</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/ppio-color.svg" width="50" height="50" alt="PPIO Icon"><br/>
      <a href="https://ppinfra.com/">PPIO</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/minimax-color.svg" width="50" height="50" alt="MiniMax Icon"><br/>
      <a href="https://platform.minimaxi.com/">MiniMax</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/fireworks-color.svg" width="50" height="50" alt="Fireworks Icon"><br/>
      <a href="https://fireworks.ai/">Fireworks</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/aihubmix.png" width="50" height="50" alt="AIHubMix Icon"><br/>
      <a href="https://aihubmix.com/">AIHubMix</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/doubao-color.svg" width="50" height="50" alt="Doubao Icon"><br/>
      <a href="https://console.volcengine.com/ark/">Doubao</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/alibabacloud-color.svg" width="50" height="50" alt="DashScope Icon"><br/>
      <a href="https://www.aliyun.com/product/bailian">DashScope</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/groq.svg" width="50" height="50" alt="Groq Icon"><br/>
      <a href="https://groq.com/">Groq</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/jiekou-color.svg" width="50" height="50" alt="JieKou.AI Icon"><br/>
      <a href="https://jiekou.ai?utm_source=github_deepchat">JieKou.AI</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/zenmux-color.svg" width="50" height="50" alt="ZenMux Icon"><br/>
      <a href="https://zenmux.ai/">ZenMux</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/github.svg" width="50" height="50" alt="GitHub Models Icon"><br/>
      <a href="https://github.com/marketplace/models">GitHub Models</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/lmstudio.svg" width="50" height="50" alt="LM Studio Icon"><br/>
      <a href="https://lmstudio.ai/docs/app">LM Studio</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/hunyuan-color.svg" width="50" height="50" alt="Hunyuan Icon"><br/>
      <a href="https://cloud.tencent.com/product/hunyuan">Hunyuan</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/302ai.svg" width="50" height="50" alt="302.AI Icon"><br/>
      <a href="https://302ai.cn/">302.AI</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/together-color.svg" width="50" height="50" alt="Together Icon"><br/>
      <a href="https://www.together.ai/">Together</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/poe-color.svg" width="50" height="50" alt="Poe Icon"><br/>
      <a href="https://poe.com/">Poe</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/vercel.svg" width="50" height="50" alt="Vercel AI Gateway Icon"><br/>
      <a href="https://vercel.com/ai">Vercel AI Gateway</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/openrouter.svg" width="50" height="50" alt="OpenRouter Icon"><br/>
      <a href="https://openrouter.ai/">OpenRouter</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/azure-color.svg" width="50" height="50" alt="Azure OpenAI Icon"><br/>
      <a href="https://azure.microsoft.com/en-us/products/ai-services/openai-service">Azure OpenAI</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/tokenflux-color.svg" width="50" height="50" alt="TokenFlux Icon"><br/>
      <a href="https://tokenflux.ai/">TokenFlux</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/burncloud-color.svg" width="50" height="50" alt="BurnCloud Icon"><br/>
      <a href="https://www.burncloud.com/">BurnCloud</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/openai.svg" width="50" height="50" alt="OpenAI Responses Icon"><br/>
      <a href="https://openai.com/">OpenAI Responses</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/cherryin-color.png" width="50" height="50" alt="CherryIn Icon"><br/>
      <a href="https://open.cherryin.ai/console">CherryIn</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/modelscope-color.svg" width="50" height="50" alt="ModelScope Icon"><br/>
      <a href="https://modelscope.cn/">ModelScope</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/aws-bedrock.svg" width="50" height="50" alt="AWS Bedrock Icon"><br/>
      <a href="https://aws.amazon.com/bedrock/">AWS Bedrock</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/voiceai.svg" width="50" height="50" alt="Voice.ai Icon"><br/>
      <a href="https://voice.ai/">Voice.ai</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/vertexai-color.svg" width="50" height="50" alt="Vertex AI Icon"><br/>
      <a href="https://cloud.google.com/vertex-ai">Vertex AI</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/githubcopilot.svg" width="50" height="50" alt="GitHub Copilot Icon"><br/>
      <a href="https://github.com/features/copilot">GitHub Copilot</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/xiaomi.png" width="50" height="50" alt="Xiaomi Icon"><br/>
      <a href="https://platform.xiaomimimo.com/#/docs/quick-start/first-api-call">Xiaomi</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/o3-fan.png" width="50" height="50" alt="o3.fan Icon"><br/>
      <a href="https://o3.fan">o3.fan</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/novitaai.svg" width="50" height="50" alt="Novita AI Icon"><br/>
      <a href="https://novita.ai/">Novita AI</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/astraflow.png" width="50" height="50" alt="Astraflow Icon"><br/>
      <a href="https://astraflow.ucloud.cn/">Astraflow</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/anthropic.svg" width="50" height="50" alt="Anthropic Icon"><br/>
      <a href="https://www.anthropic.com/">Anthropic</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/siliconcloud-color.svg" width="50" height="50" alt="SiliconFlow Icon"><br/>
      <a href="https://www.siliconflow.cn/">SiliconFlow</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/orcarouter.svg" width="50" height="50" alt="OrcaRouter Icon"><br/>
      <a href="https://www.orcarouter.ai/">OrcaRouter</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/synthorai.svg" width="50" height="50" alt="Synthorai Icon"><br/>
      <a href="https://synthorai.io/">Synthorai</a>
    </td>
  </tr>
  <tr align="center">
    <td>
      <img src="./src/renderer/src/assets/llm-icons/requesty.png" width="50" height="50" alt="Requesty Icon"><br/>
      <a href="https://www.requesty.ai/">Requesty</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/litellm.svg" width="50" height="50" alt="LiteLLM Icon"><br/>
      <a href="https://www.litellm.ai/">LiteLLM</a>
    </td>
    <td>
      <img src="./src/renderer/src/assets/llm-icons/opper.svg" width="50" height="50" alt="Opper Icon"><br/>
      <a href="https://opper.ai/">Opper</a>
    </td>
  </tr>

</table>

### Compatible with any model provider in OpenAI/Gemini/Anthropic API format

Any service that speaks the OpenAI, Gemini, or Anthropic API format can be added as a custom provider next to the built-in ones.

## 🔍 Use Cases

DeepChat is suitable for various AI application scenarios:

- **Daily Assistant**: Answering questions, providing suggestions, assisting with writing and creation
- **Development Aid**: Code generation, debugging, technical problem solving
- **Learning Tool**: Concept explanation, knowledge exploration, learning guidance
- **Content Creation**: Copywriting, creative inspiration, content optimization
- **Data Analysis**: Data interpretation, chart generation, report writing

## 💻 Development Guide

Please read the [Contribution Guidelines](./CONTRIBUTING.md)

Windows and Linux are packaged by GitHub Action.
For Mac-related signing and packaging, please refer to the [Mac Release Guide](https://github.com/ThinkInAIXYZ/deepchat/wiki/Mac-Release-Guide).

### Install Dependencies

```bash
$ pnpm install
$ pnpm run installRuntime
# if got err: No module named 'distutils'
$ pip install setuptools
```

* For Windows: To allow non-admin users to create symlinks and hardlinks, enable `Developer Mode` in Settings or use an administrator account. Otherwise `pnpm` ops will fail.

### Start Development

```bash
$ pnpm run dev
```

### Build

```bash
# For Windows
$ pnpm run build:win

# For macOS
$ pnpm run build:mac

# For Linux
$ pnpm run build:linux

# Specify architecture packaging
$ pnpm run build:win:x64
$ pnpm run build:win:arm64
$ pnpm run build:mac:x64
$ pnpm run build:mac:arm64
$ pnpm run build:linux:x64
$ pnpm run build:linux:arm64
```

For a more detailed guide on development, project structure, and architecture, please see the [Developer Guide](./docs/guides/getting-started.md) and the maintained [documentation index](./docs/README.md).

## 👥 Community & Contribution

DeepChat is an active open-source community project, and we welcome various forms of contribution:

- 🐛 [Report issues](https://github.com/ThinkInAIXYZ/deepchat/issues)
- 💡 [Submit feature suggestions](https://github.com/ThinkInAIXYZ/deepchat/issues)
- 🔧 [Submit code improvements](https://github.com/ThinkInAIXYZ/deepchat/pulls)
- 📚 [Improve documentation](https://github.com/ThinkInAIXYZ/deepchat/wiki)
- 🌍 [Help with translation](https://github.com/ThinkInAIXYZ/deepchat/tree/main/src/renderer/src/i18n)

Check the [Contribution Guidelines](./CONTRIBUTING.md) to learn more about ways to participate in the project.

## ⭐ Star History

[](https://star-history.dera.page/#ThinkInAIXYZ/deepchat&Timeline)

## 👨‍💻 Contributors

Thank you for considering contributing to deepchat! The contribution guide can be found in the [Contribution Guidelines](./CONTRIBUTING.md).

<a href="https://openomy.com/thinkinaixyz/deepchat" target="_blank" style="display: block; width: 100%;" align="center">
  <img src="https://openomy.com/svg?repo=thinkinaixyz/deepchat&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
</a>

## 🙏🏻 Thanks

This project is built with the help of these awesome libraries and projects:

- [Vue](https://vuejs.org/)
- [Electron](https://www.electronjs.org/)
- [Electron-Vite](https://electron-vite.org/)
- [oxlint](https://github.com/oxc-project/oxc)
- [Bub](https://github.com/bubbuild/bub), whose tape model inspired DeepChat's session tape design. For the underlying tape architecture, visit [tape.systems](https://tape.systems/).

## 📃 License

[LICENSE](./LICENSE)

---

### Overview (`docs/README.md`)

# DeepChat 文档索引

This index tracks maintained architecture and outstanding acceptance work.
Completed implementation trackers and resolved issue reports are available through Git history.

## 当前必读

| 文档                                                                        | 用途                                                                                |
| --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| [Module performance audit](./architecture/module-performance-audit/spec.md) | Module coverage, confirmed repairs, test evidence, and remaining measurement limits |
| [ARCHITECTURE.md](./ARCHITECTURE.md)                                        | main 进程模块、所有权、生命周期和依赖方向                                           |
| [FLOWS.md](./FLOWS.md)                                                      | 启动、Session、Agent、Tool、Remote、Scheduler、Sync 和退出流程                      |
| [architecture/agent-system.md](./architecture/agent-system.md)              | DeepChat / ACP backend、Run、权限和 Subagent 合同                                   |
| [architecture/session-management.md](./architecture/session-management.md)  | Session 数据、binding、恢复、删除和 transfer                                        |
| [architecture/tool-system.md](./architecture/tool-system.md)                | Tool、MCP、Skill、Plugin 和权限边界                                                 |
| [architecture/memory-system.md](./architecture/memory-system.md)            | Memory 存储、检索、写入、隔离和维护                                                 |
| [architecture/tape-system.md](./architecture/tape-system.md)                | Tape、ViewManifest、回放和 Subagent lineage                                         |
| [architecture/event-system.md](./architecture/event-system.md)              | typed route、typed event 和 main 内部调用规则                                       |
| [architecture/shared-skills/spec.md](./architecture/shared-skills/spec.md)  | 全局 Skills、Agent binding、迁移和运行时授权合同                                    |
| [guides/getting-started.md](./guides/getting-started.md)                    | 当前代码入口和本地开发命令                                                          |
| [guides/cli.md](./guides/cli.md)                                            | 随包 CLI 的能力、生命周期、安全边界和 benchmark 合同                                |
| [guides/plugin-packaging.md](./guides/plugin-packaging.md)                  | `.dcplugin` 打包、内置分发和 release 规则                                           |
| [release-flow.md](./release-flow.md)                                        | 版本、分支、tag 和平台构建流程                                                      |
| [spec-driven-dev.md](./spec-driven-dev.md)                                  | SDD 分类、产物和清理规则                                                            |

## 进行中的目标

新建或继续维护的 feature / architecture 以 `plan.md` 作为唯一执行清单；有界复杂 bug 可在
`spec.md` 中保留简短清单。历史 `tasks.md` 仅在对应目标下次更新时迁移：优先并入已有
`plan.md`；没有 plan 的单阶段复杂 bug 并入 `spec.md`，其他目标创建 `plan.md`。不为规范迁移
单独批量改写。

| 文档                                                                                                           | 状态                                                                                           |
| -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| [features/acp-v1-reliability/](./features/acp-v1-reliability/)                                                 | Implemented capability, auth, session lifecycle, and diagnostics; acceptance follow-ups remain |
| [features/cua-cross-platform-computer-use/](./features/cua-cross-platform-computer-use/)                       | 已实现主体，等待 CI platform matrix 验证                                                       |
| [features/mcp-oauth-authentication/](./features/mcp-oauth-authentication/)                                     | 已实现主体，等待真实 OAuth smoke                                                               |
| [architecture/mcp-v2-protocol/](./architecture/mcp-v2-protocol/)                                               | v2 与 legacy wire 已落地，等待外部互操作验证及兼容窗口结束                                     |
| [features/mcp-apps/](./features/mcp-apps/)                                                                     | MCP Apps host 已落地，等待 packaged sandbox 与外部 App 验证                                    |
| [features/mcp-authorization-extensions/](./features/mcp-authorization-extensions/)                             | 授权扩展已落地，等待受控 OAuth 与安全存储验证                                                  |
| [features/mcp-tasks/](./features/mcp-tasks/)                                                                   | Tasks 被上游公开 v2 adapter 阻塞，未实现、未宣称支持                                           |
| [architecture/chat-scroll-ownership/](./architecture/chat-scroll-ownership/)                                   | chat viewport ownership、windowing 与真实 Chromium 验证进行中                                  |
| [architecture/memory-quality-gates-and-observability/](./architecture/memory-quality-gates-and-observability/) | retrieval artifact upload 待完成                                                               |
| [architecture/memory-vector-store-v2/](./architecture/memory-vector-store-v2/)                                 | v2 已落地，保留 migration window 后的 VSS removal follow-up                                    |
| [issues/chat-history-search-scroll-coordinates/](./issues/chat-history-search-scroll-coordinates/)             | 等待 Electron/macOS 物理滚动验证                                                               |

## 保留的产品合同

以下 feature spec 仍承担跨模块产品或扩展合同，不是实施历史：

- [Accessibility](./features/accessibility/spec.md)
- [Local Control Plane](./architecture/local-control-plane/spec.md)
- [ACP Terminal Authentication](./features/acp-terminal-auth/spec.md)
- [Main Structured Logging](./architecture/main-process-structured-logging/spec.md)
- [Provider Runtime](./features/provider-runtime/spec.md)
- [DeepChat Skills Management](./features/deepchat-skills-management/spec.md)
- [Plugins Hub](./features/plugins-hub/spec.md)
- [Complete Directory Management](./features/complete-directory-management/spec.md)
- [MCP Permission Ownership](./architecture/remove-mcp-permission-system/spec.md)

## 机器读取基线

| 文件                                                                                                              | 使用方                                                       |
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| [agent-system-layered-runtime-baseline.json](./architecture/baselines/agent-system-layered-runtime-baseline.json) | architecture baseline generator test 的 canonical fixture    |
| [main-kernel-bridge-register.json](./architecture/baselines/main-kernel-bridge-register.json)                     | architecture baseline generator 的 retired boundary register |

其它 dependency、scoreboard、zero-inbound 报表由 `pnpm run architecture:baseline` 按需生成，
不作为长期文档。

## 文档保留规则

- 当前事实写入核心 architecture、flow 或 guide，不再保留重复的 implemented SDD。
- 完成的 feature / architecture 删除 `plan.md` 和遗留 `tasks.md`；只有维护合同需要时保留
  压缩后的 `spec.md`。
- 已修复 issue 直接删除；历史由 Git 保存，必要的持久化回归由测试保护。
- active goal、`plan.md` 中的未完成步骤、遗留未完成 task 和 `[NEEDS CLARIFICATION]` 不得在
  cleanup 中删除。
- 文档引用的路径必须存在；旧实现只通过 `git log` / `git show` 查询。

---

### Overview (`test/README.md`)

# DeepChat tests

Tests protect user workflows and maintained contracts. Completing a feature does not retire its
regression coverage. Follow the repository's [validation policy](../docs/spec-driven-dev.md).

## Scope and commands

Run commands from the repository root with pnpm and the Node version declared in `package.json`.
Install dependencies with `pnpm install`; install optional application runtimes with
`pnpm run installRuntime` when the selected verification needs them.

| Scope | Command | Boundary |
| --- | --- | --- |
| Main and renderer | `pnpm test` | Vitest projects in `vitest.config.ts` |
| Main, shared contracts and scripts | `pnpm run test:main` | Node; `test/main` |
| Renderer | `pnpm run test:renderer` | Vue Test Utils and jsdom; `test/renderer` |
| Portable Memory | `pnpm run test:memory` | Scope validation, test type checking and portable suites |
| Electron smoke | `pnpm run e2e:smoke` | Built app; see [E2E setup and isolation](./e2e/README.md) |
| CI Electron subset | `pnpm run e2e:smoke:ci` | Launch and Settings navigation |
| Coverage | `pnpm run test:coverage` | Reports under `coverage/` |
| Interactive watch | `pnpm run test:watch` | Explicit watch mode |

Run the smallest relevant target while working:

```bash
pnpm exec vitest run --config vitest.config.ts test/main/session/lifecycle.test.ts
pnpm exec vitest run --config vitest.config.renderer.ts test/renderer/stores/sessionStore.test.ts
```

Native SQLite's Node ABI rebuild and required native validation belong to CI. Do not rebuild the
shared dependency merely to run a local test; that can replace the Electron ABI. Native and
platform-gated suites can skip when their prerequisites are unavailable. A skipped test is not
passing evidence for that platform. Memory scope classification lives in
[`memory-test-scope.json`](./memory-test-scope.json).

## Durable coverage

| Group | Protected capabilities |
| --- | --- |
| Agent, session and Tape | Admission, cancellation, concurrency, recovery, projection and durable history |
| Provider, ACP, MCP, tools and plugins | Protocol compatibility, permissions, credentials, lifecycle and failure isolation |
| Memory, storage, sync and import | Isolation, migrations, corruption recovery and persisted data |
| Desktop, preload, routes and renderer clients | Caller authorization, serializable contracts, event delivery and cleanup |
| Renderer components, stores and composables | Keyboard and focus behavior, accessible content, drafts, configuration and asynchronous state |
| Build and scripts | Package integrity, supported targets, signing, updater compatibility and required CI gates |
| Electron smoke | Application wiring and workflows through real renderer/preload/main boundaries |

Keep real domain logic, stores and temporary files where they provide useful behavior coverage.
Substitute network, model, OS and other expensive or nondeterministic boundaries. Interaction
assertions are appropriate when the interaction is the contract, such as authorization,
idempotency or protocol calls.

Delete a test only with evidence that it is obsolete, vacuous, fully redundant, or merely locks an
incidental implementation choice. Identify retained protection or the precise coverage gap first.
Do not keep source-string checks for prose, retired migration filenames or private assignment
counts. Source inspection remains useful for enforced import boundaries, documented visual or
startup regressions, and machine-read packaging or workflow contracts.

Prefer assertions on public results, persisted data, emitted events, rendered semantics and
recoverable errors. jsdom does not verify native window focus, screen-reader speech or computed
visual layout; use the appropriate Electron or manual acceptance for those behaviors. Avoid
adding empty examples, temporary probes or implementation-mirroring tests to the committed suite.

Before handoff, run `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, `pnpm run typecheck`, and the
relevant tests. Application type checking does not automatically typecheck every test file;
type-only assertions need an explicit type-checking target to provide evidence.

## Manual deeplink verification

Open [`manual/deeplink-playground.html`](./manual/deeplink-playground.html) in a browser while
DeepChat is running. It provides fake payloads for `deepchat://start`, `deepchat://mcp/install` and
`deepchat://provider/install`, including built-in providers and custom API types. Allow the browser
to open `deepchat://` links when prompted.

---

### Overview (`test/e2e/README.md`)

# DeepChat E2E Smoke

The suite launches the built Electron application. By default, each fixture creates a temporary
`userData` profile, seeds completed onboarding and diagnostic logging, and removes the profile
after closing the app. It does not use your normal desktop profile by default.

## Coverage

The default suite covers startup, Settings navigation, typed IPC boundaries, workspace watchers,
provider configuration, agent and plugin management, composer drafts, and local streaming.
The plugin and streaming specs use loopback HTTP/MCP fixtures through the real application routes.
They require no external provider credentials.

Five specs require `RUN_PROVIDER_INTEGRATION=true`: basic chat, session persistence, provider
connectivity, chat scrollbar ownership, and composer width. Their default provider/model is
`minimax` / `MiniMax-M2.7`; override these with `DEEPCHAT_E2E_PROVIDER_ID` and
`DEEPCHAT_E2E_MODEL_ID`. The target model must be configured and enabled in the selected profile.

The CI subset runs launch, Settings navigation, and Skill sync, using the same profile isolation.
Skill sync asserts that scanning does not fall back from the bundled Worker. The Windows ARM64
workflow also runs this spec against the packaged executable to cover loading from `app.asar`.

## Commands

Use Node >=20.19 and pnpm >=10.11. Build before running:

```bash
pnpm run build
pnpm run e2e:smoke
```

Run the smaller CI subset:

```bash
pnpm run e2e:smoke:ci
```

Run a specific deterministic workflow:

```bash
pnpm exec playwright test -c test/e2e/playwright.config.ts 36-chat-streaming
```

For live provider checks, select a dedicated configured test profile:

```bash
DEEPCHAT_E2E_USER_DATA_DIR=/absolute/path/to/test-profile \
RUN_PROVIDER_INTEGRATION=true \
pnpm run e2e:smoke
```

`DEEPCHAT_E2E_USER_DATA_DIR` disables fixture ownership and automatic profile removal. Tests can
create sessions and change application state in that directory. The fixture seeds settings only
when `app-settings.json` does not already exist.

For a packaged executable, set `DEEPCHAT_E2E_APP_MODE=packaged` and
`DEEPCHAT_E2E_EXECUTABLE_PATH` to its absolute path. Windows defaults to the matching unpacked
architecture under `dist` when no executable override is supplied.

## Artifacts and state contracts

Results are written to `test-results/e2e` and `playwright-report`. Each test attaches renderer
console output, page errors, and available main-process logs; failures retain screenshots,
video, and traces.

`chat-page-shell` owns `data-generating`. `chat-page` is the message scroll viewport.
The generation helper observes the shell's generating-to-idle transition. The local streaming
spec holds its response open while checking composer input, then releases it and verifies both
completion and preservation of the draft.

---

### Overview (`examples/user-plugins/baizhi-agent-toolkit/README.md`)

# Baizhi Cloud Agent Toolkit for DeepChat

[简体中文](./README.zh-CN.md)

An optional, MCP-only user plugin contributed by Baizhi Cloud. It connects DeepChat directly to
[Agent Toolkit](https://baizhi.cloud/landing/agent-toolkit), a hosted commercial tool service for
search, webpage/document processing and other tasks. It is not a model provider, a bundled
DeepChat plugin, or a listing in the MCPRouter store.

The package contains only a manifest and remote MCP declaration, plus documentation and a license.
It has no local commands, hooks, Skills, install scripts or automatic tool-approval rules.
Installing it does not enable it or change your model settings.

## Requirements and data use

- Use a DeepChat version with **Plugins → Install from ZIP** and user-plugin
  MCP credential setup. This flow is available in `v1.1.2-beta.5`; `v1.1.1` does not include it.
  The source revision used to build the ZIP must also contain this example directory.
- Create your own account and API key in the [Baizhi Cloud console](https://agent-toolkit.app.baizhi.cloud/).
  Review the service terms, permissions, balance and usage limits. Calls may consume credits.
- The endpoint is `https://agent-toolkit.app.baizhi.cloud/mcp`, using Streamable HTTP and Bearer
  authentication. There is no MCPRouter account, intermediary key or OAuth sign-in in this flow.
- Tool inputs, including search queries, URLs, documents and extraction requests, are sent to
  Baizhi Cloud when you invoke the corresponding tools. Do not submit sensitive data without
  permission. Review the current service privacy information before use; this example makes no
  promises about retention, residency or zero logging.

## Install and enter your key

1. Obtain a source checkout containing this example and create a ZIP containing **only this
   directory**, including hidden files. From the repository root on a system with `zip`:

   ```sh
   cd examples/user-plugins/baizhi-agent-toolkit
   archive_dir="$(mktemp -d)"
   zip -r "$archive_dir/baizhi-agent-toolkit.zip" .codex-plugin .mcp.json README.md README.zh-CN.md LICENSE
   ```

   For a review build, use the contributor's exact PR commit. A pending PR is not part of upstream
   `dev`. Check the source before packaging; never include `.env`, credentials, `.git`,
   `node_modules` or an entire repository checkout.
2. Open **Plugins → Install from ZIP**, select the resulting archive and review the package.
   It should declare just the `agent-toolkit` MCP server, the HTTPS endpoint
   above, and the required variable `BAIZHI_API_KEY`. Select **MCP** and confirm installation;
   this package has no Skills or hooks to select. The plugin is initially **disabled**.
3. Open the installed **baizhi-agent-toolkit** plugin and explicitly enable it. Its MCP setup
   section's `BAIZHI_API_KEY` password field becomes editable.
4. Enter the **raw API key only** in that password field, without `Bearer`, quotes or a full
   Authorization header, and click **Save and reconnect**. Do not put the key in either JSON file, a chat,
   an issue, or a screenshot. The variable name is a binding identifier; setting an operating
   system environment variable does not configure a user plugin.
5. Check the MCP status card on the plugin detail page for running state or an error; use its
   reload action if necessary. In a conversation using DeepChat's built-in agent, open
   **Advanced Settings → Plugins** to check the discovered tool count. Installation or saving a
   key alone is not evidence that the server authenticated successfully. The ordinary MCP
   settings list excludes plugin-owned servers; do not use that list to judge this plugin's state.

The existing DeepChat credential form stores the value separately through its SecretStore;
the MCP configuration retains `Authorization: Bearer ${BAIZHI_API_KEY}`. Stored values are not
shown back in the form. Platform encryption availability is handled by DeepChat; if saving fails,
resolve the reported credential-storage problem rather than putting the key in the manifest.

Do not use the entire DeepChat repository with **Install from Git** for this example: the current
installer checks the whole repository archive before selecting a package subdirectory, and that
archive exceeds the plugin file-count limit. Packaging only this example avoids that limitation
without weakening installation checks.

## Verify and control tool use

After the connection succeeds, confirm the plugin appears with discovered tools in the built-in
agent's **Advanced Settings → Plugins** group and review any permission prompts when using them.
That group shows the tool count, not a per-tool selection checklist. This example does not configure external ACP agents. A small,
non-sensitive first task can use `websearch_search`,
`web_scrape` or `web_extract`, if your account exposes them. Check the result and console usage.
Discovery does not prove that every tool is authorized or that the account has sufficient credit.

These three names are suggested starting points, **not a configured allowlist**. The plugin imports
the service's discovered tools, whose availability depends on the service and your key. It does
not auto-approve calls or override DeepChat's tool-selection and permission controls.

## Rotate, disable and remove

- **Rotate:** create a replacement key in Baizhi Cloud, enter it in the same plugin MCP setup form,
  save and verify the new connection before revoking the previous key. Existing values are not
  displayed for editing.
- **Disable:** disable the plugin to stop its integration. This does not revoke the cloud key.
- **Remove:** uninstall the plugin to remove its owned local configuration and bindings. Revoke
  the key in Baizhi Cloud if it is no longer needed. Uninstalling does not erase prior conversation
  records or revoke credentials held by other applications.
- **Update:** use **Review update**, inspect the changes, and explicitly approve the new snapshot.
  A changed package snapshot can require credential setup again, even for a documentation-only
  update with the same endpoint and credential declaration. The saved credential remains in
  local storage, but the updated plugin's configuration no longer matches its binding. After
  updating, check the plugin's setup status and re-enter your raw key in DeepChat's native
  credential form if requested. This example does not change that behavior. Updating a source
  checkout does not silently update an installed plugin.

## Troubleshooting

- **No install or credential UI:** check the DeepChat version and that this is installed as a user
  plugin, not pasted into the ordinary MCP configuration editor or MCPRouter store.
- **Missing variable:** enable the plugin, enter a nonempty key in its own MCP setup form, and save.
- **401 / unauthorized:** confirm the key is current and entered without the `Bearer` prefix.
- **Connected but a tool fails:** check the key's permissions, quota, balance and required tool inputs.
  Avoid repeated retries of a potentially billable call.
- **Connection failure:** check network access to the fixed HTTPS endpoint and the connection
  diagnostic. Redact credentials and sensitive tool inputs before sharing logs.

For service/account support, use the [Baizhi Cloud integration repository](https://github.com/chaitin/baizhi-agent-toolkit).
For DeepChat installation behavior, see the repository's
[user-plugin guide](https://github.com/ThinkInAIXYZ/deepchat/blob/dev/docs/features/user-plugins/authoring.md).
When reporting an issue, include versions and redacted diagnostics, never a real API key.

These example files are licensed under [Apache-2.0](./LICENSE), consistent with DeepChat.
The hosted service is separately operated by Baizhi Cloud and subject to its own terms; its backend
source is not included. Providing this example does not imply DeepChat certification or endorsement.

---

### Overview (`examples/user-plugins/parallel-search/README.md`)

# Parallel Search for DeepChat

An optional MCP-only user plugin for free web search and page fetching with
[Parallel Search MCP](https://docs.parallel.ai/integrations/mcp/search-mcp).
No account or API key is required. Anonymous searches use Fast mode; the free endpoint has
rate limits and is intended for exploration and light use.

The package contains a manifest and remote MCP configuration, with no local commands, hooks,
Skills or dependencies to install. Installation leaves the plugin disabled and preserves your
existing model and MCP settings. It connects directly to `https://search.parallel.ai/mcp` using
Streamable HTTP. Search queries and requested URLs are sent to Parallel when tools are called.

## Install

Use a DeepChat version with **Plugins → Install from ZIP**. The user-plugin installation flow
is available in `v1.1.2-beta.5` and later; `v1.1.1` does not include it.

1. Obtain a source checkout containing this example. For a pending PR, use its exact commit;
   the example will only be available in upstream `dev` after merge.
2. Package only this directory, including its hidden files. With Python 3, run from the
   repository root:

   ```sh
   cd examples/user-plugins/parallel-search
   python3 -m zipfile -c /tmp/parallel-search.zip .codex-plugin .mcp.json README.md LICENSE
   ```

   Choose another output path on Windows. Do not include the entire repository, `.git`,
   credentials or `node_modules`. **Install from Git** on the whole DeepChat repository exceeds
   the current plugin file-count limit; use the small ZIP instead.
3. Open **Plugins → Install from ZIP**, select the archive and review the package. It should
   contain only the `parallel-search` HTTP MCP server at the endpoint above, with no credential
   variables, Skills or hooks. Select **MCP** and confirm installation.
4. Explicitly enable the installed **parallel-search** plugin. No password or OAuth sign-in is
   needed. Check its MCP status card for a running connection and discovered tools. Plugin-owned
   servers do not appear in the ordinary MCP settings list.

## Tools and controls

The endpoint exposes `web_search` for finding pages and `web_fetch` for reading URLs. Under the
discovered tool schemas, `web_search` accepts arguments such as:

```json
{
  "objective": "Find the Python documentation for virtual environments",
  "search_queries": ["Python virtual environment documentation"]
}
```

Then call `web_fetch` with a URL from the result:

```json
{
  "urls": ["https://docs.python.org/3/library/venv.html"],
  "objective": "Explain how to create a virtual environment"
}
```

Inspect the returned content. Discovery alone does not prove a tool call succeeded. This package
uses DeepChat's existing tool and permission controls and does not configure external ACP agents.

Disable the plugin to stop its integration, or uninstall it to remove its local configuration.
Use **Review update** to import changes; editing your source checkout does not update an installed
snapshot. Uninstalling does not remove previous conversation records.

If a call is rate limited, wait for the service's retry time before trying again. For connection
errors, check access to the endpoint and the plugin's diagnostic. Do not add an Authorization
header to this anonymous example. For service limits and support, see the
[Parallel Search MCP guide](https://docs.parallel.ai/integrations/mcp/search-mcp); for installation
behavior, see DeepChat's [user-plugin guide](../../../docs/features/user-plugins/authoring.md).

These example files use DeepChat's [Apache-2.0 license](./LICENSE). Parallel operates the hosted
service separately. I work at Parallel.

---

### Overview (`examples/user-plugins/sem/README.md`)

# Sem local code review for DeepChat

An optional MCP-only plugin for [Sem](https://github.com/Ataraxy-Labs/sem): inspect
code entities, callers, dependencies and structural diffs in a local Git repository.
No Sem account or API key is required. This example adds no hooks, Skills or bundled binaries.
It leaves existing model settings and MCP servers unchanged.

## Install

Use a DeepChat version with user plugins (v1.1.2-beta.5 or later), Git, and Node.js/npm
available through DeepChat's Toolchains settings. This example targets macOS/Linux on ARM64/x64.
Enabling the server invokes npx; first installation downloads the pinned npm package and native
Sem binary. Only connect a repository whose build/test scripts you trust.

1. Obtain a checkout containing this example. Until its PR merges, use the PR's exact commit.
2. From this directory, package the hidden files as well as the README:

   ```sh
   python3 -m zipfile -c /tmp/sem-code-review.zip .codex-plugin .mcp.json README.md
   ```

   Choose a suitable output path on your platform. Do not package the whole repository,
   credentials, `.git` or `node_modules`.
3. Open **Plugins → Install from ZIP** and inspect the archive. Expect one stdio server,
   the pinned command above, and one required variable, `SEM_REPO`. Select MCP and install.
   Installation leaves the plugin disabled.
4. Explicitly enable the plugin and use its MCP setup form to supply `SEM_REPO`: an absolute
   path to an existing local Git repository. The generic setup form masks variable values;
   this value is a directory path, not an API key. It is not inherited from your shell.
5. Check the plugin's MCP connection status and discovered tool schemas. Plugin-owned servers
   are separate from the ordinary MCP settings list. Discovery is not proof that a tool call works.

## Try it

Substitute a symbol that exists in the selected repository:

> Use Sem to locate `parse_config`, inspect its callers and explain the code. Do not edit files or run tests.

For a checkout with tracked local changes:

> Inspect the structural diff with Sem and explain the changes. Do not execute validation commands.

The graph's coverage depends on the language and source constructs. Missing results do not prove
there are no references. Retain ordinary file tools for configuration and unresolved code.
This plugin does not configure external ACP agents or provide Weave editing/merging tools.

## Permissions and privacy

- `sem_check` can run repository build/test commands. Keep normal tool approvals and only
  authorize execution intentionally; this is not a read-only server.
- `SEM_REPO` selects a repository; it is not an operating-system sandbox.
- The preset disables Sem cloud access and telemetry recording. npm/native-binary installation
  still uses the network. Those flags do not sandbox commands run by repository checks.
- Returned source code enters the conversation and may reach the configured model provider.
- Sem builds a local index. Structural results are not proof that code compiles or passes tests.

If setup fails, check Node/npm in Toolchains, Git availability, install-script permissions and the
absolute repository path. Change the path through MCP setup, not your shell environment.
Use **Review update** to change an installed package; editing the source checkout does not update
its installed snapshot. Disable or uninstall the plugin to remove its active integration.
Uninstalling does not erase earlier conversations, npm downloads or Sem's local cache.

The example files use DeepChat's Apache-2.0 license; Sem is distributed separately under its own
license. This example is contributed by Sem's maintainer at Ataraxy Labs.

---

### Overview (`plugins/cua/skills/computer-use/README.md`)

# Computer Use Workflow

This skill uses DeepChat's plugin-provided Computer Use tools.

Core workflow:

1. `start_session`
2. `list_apps`
3. `launch_app`
4. `list_windows`
5. `get_window_state`
6. UI action tool
7. inspect `## CUA action result` when the invoked tool uses that contract
8. `verify_state` for an expressible exact-window postcondition, otherwise a fresh state tool
9. `end_session`

Prefer a non-empty opaque element token from the latest snapshot for the same `pid` and
`window_id`. An index fallback must include both `element_index` and the exact `snapshot_id` from
that same snapshot; a bare index is invalid. When a local `snapshot_id_required` error or a
model-visible addressing `refusal.code` appears, take one fresh snapshot and retry only with a
token or index-plus-snapshot pair entirely from the replacement result. Use pixel coordinates when
an explicitly requested screenshot clearly shows a target missing from the accessibility tree.

Action delivery is not task completion. Treat only `verify_state` status `satisfied` with
`stable=true` as deterministic window-state success; use fresh window, browser, or desktop state
for effects outside its predicate contract. `clipboard_read` is intentionally denied because it
can expose privacy-sensitive plaintext.

Close apps cooperatively and verify exit.

For Chromium-family page content, bind the exact native window with `get_browser_state` and use the
typed `browser_*` tools. The legacy `page` tool is compatibility-only.

Supported bundled targets:

- `darwin/arm64`
- `darwin/x64`
- `win32/x64`
- `win32/arm64`
- `linux/x64`

Unsupported bundled targets:

- `linux/arm64`

Do not ask the user to install CUA manually for DeepChat's bundled plugin.

---

### Guides Getting Started (`docs/guides/getting-started.md`)

# 快速入门

## 环境

- Node.js `>=24.18.0 <25`
- pnpm `>=10.34.5 <11`
- Git

```bash
pnpm install
pnpm run hooks:install
pnpm run installRuntime
pnpm run dev
```

## 当前心智模型

```text
Vue component / Pinia store
  -> src/renderer/api/*Client
  -> window.deepchat
  -> src/shared/contracts/routes + events
  -> module routes
  -> owning main module
```

main 进程只有一个 composition root：`src/main/app/composition.ts`。它创建模块、注入窄依赖、注册
route、排定 start/stop，但不向业务代码提供模块查找入口。

```text
src/main/
├── app/          # process composition and maintenance
├── desktop/      # window, tab, tray, shortcut, renderer binding
├── session/      # lifecycle, turn, assignment, query and durable data
├── agent/        # DeepChat and ACP backends
├── provider/     # provider config, auth, model and execution
├── tool/         # tool catalog, permission and built-in tools
├── mcp/          # MCP config, OAuth, client/server and tools
├── skill/        # Skill files, scan and sync
├── plugin/       # plugin package and contribution registration
├── memory/       # long-term Memory
├── knowledge/    # built-in knowledge base
├── workspace/    # workspace authorization and file tree/search
├── file/         # file adapters and conversion
├── remote/       # remote channels and endpoint binding
├── scheduler/    # Cron jobs, runs and delivery
├── sync/         # backup/import/cloud sync
└── routes/       # shared route registry mechanics only
```

`src/main/presenter/`、全局 `Presenter`、`LifecycleManager`、全局 `EventBus`、
`useLegacyPresenter()` 和 `src/renderer/api/legacy/**` 已删除。不要从旧提交里的这些名字推断当前入口。

## 推荐阅读顺序

1. `docs/ARCHITECTURE.md`
2. `docs/FLOWS.md`
3. `src/shared/contracts/routes.ts`
4. `src/shared/contracts/events.ts`
5. `src/preload/createBridge.ts`
6. `src/renderer/api/`
7. `src/main/app/composition.ts`
8. 对应模块的 `routes.ts` 和 owner

常见任务入口：

| 任务 | 入口 |
| --- | --- |
| 发送、停止、交互回复 | `src/main/session/turn.ts`、`src/main/session/chatService.ts` |
| Session create/restore/delete | `src/main/session/` |
| DeepChat loop | `src/main/agent/deepchat/loop/`、`runtime/` |
| Direct ACP | `src/main/agent/acp/instance/`、`runtime/` |
| Provider/auth/model | `src/main/provider/` |
| Agent tools | `src/main/tool/agentTools/` |
| MCP | `src/main/mcp/` |
| Memory | `src/main/memory/`、`src/main/agent/deepchat/memory/` |
| import/export | `src/main/app/startupMigrations/`、`src/main/exporter/` |
| Renderer chat UI | `src/renderer/src/features/chat-page/ChatPage.vue`、`components/message/` |

## 质量门

完成 feature 后运行：

```bash
pnpm run format
pnpm run i18n
pnpm run lint
pnpm run typecheck
```

再按改动范围运行 Vitest。涉及 Agent legacy boundary 时补跑：

```bash
pnpm run lint:agent-cleanup
```

涉及 baseline 时使用 `pnpm run architecture:baseline`。除两个 machine-read JSON 外，生成的 Markdown
报表只用于当次审计。

历史设计和已删除 SDD 通过 `git log -- docs`、`git show <commit>:<path>` 查询。

---

### ARCHITECTURE (`docs/ARCHITECTURE.md`)

# DeepChat 当前架构概览

本文档描述 `2026-07-16` 的 main 进程实际结构。旧的全局 `Presenter`、
`LifecycleManager`、全局 `EventBus` 和业务模块查找入口已经删除。

## 总体结构

```mermaid
flowchart TD
    Renderer["Renderer"] --> Preload["Preload bridge"]
    Preload --> Contracts["shared typed routes / events"]
    Contracts --> RouteMaps["各模块 route map"]
    RouteMaps --> Modules["负责该行为的模块"]

    App["App composition"] --> Platform["Platform / Settings / Data"]
    App --> Capabilities["Provider / Tool / MCP / Skill / Plugin / Memory / Knowledge / Workspace"]
    App --> Agent["Agent: DeepChat / ACP"]
    App --> Session["Session"]
    App --> Entries["Desktop / Remote / Scheduler / Deeplink"]

    Entries --> Session
    Session --> Agent
    Agent --> Capabilities
    Capabilities --> Platform
```

`src/main/app/composition.ts` 是唯一的组合入口。它创建模块、传入明确依赖、注册 route、
排定启动与停止顺序，但不导出模块列表，也不提供按名称查找模块的方法。

依赖方向是：

```text
App composition
  -> Desktop / Remote / Scheduler / Deeplink
  -> Session
  -> Agent runtime
  -> Provider / Tool / MCP / Skill / Plugin / Memory / Knowledge / Workspace
  -> Platform / Settings / Data
```

下层模块不能反向读取 App、Desktop、Remote 或 Scheduler。需要通知 renderer 时，App 在创建模块时
传入有类型的发送函数。

## 生命周期

### App

`src/main/appMain.ts` 负责 Electron 进程入口、single-instance、deeplink 缓存和退出请求。
`src/main/app/mainProcess.ts` 负责数据库解锁、连接、迁移和启动失败清理。
`src/main/app/composition.ts` 负责创建、连接、启动和停止业务模块。

`startMainProcess()` 只返回 `MainProcessControl`。它只能聚焦主窗口、处理 deeplink、清理权限、
确认退出、查询主窗口和停止 main 进程，不能读取业务模块。

### Session

`src/main/session/` 负责可长期保存的 Session 规则：

- `lifecycle.ts`：创建、草稿、关闭和基础生命周期；
- `turn.ts`：发送、排队、停止和交互回复；
- `assignment.ts`：Agent、model、project、fork 和 subagent 结果处理；
- `query.ts`：不会偷偷载入 Agent 的查询；
- `deletion.ts`：删除顺序和两类 backend 清理；
- `data/`：transcript、Tape、pending input、settings、search 和 trace。

窗口与 Session 的绑定不在 Session 数据中，由 `DesktopSessionBinding` 负责。窗口关闭不会默认删除
Session，也不会默认停止仍由其他入口使用的任务。

### Agent

`AgentManager` 根据 `AgentDescriptor.kind` 选择两套独立实现：

- `DeepChat`：`DeepChatAgentRuntime`、`DeepChatAgentInstance` 和 `DeepChatLoopEngine`；
- `ACP`：`AcpAgentRuntime`、`AcpAgentInstance` 和 ACP protocol runtime。

一个已载入的 Session 只有一个对应 instance。每次 Turn 使用独立 Run 保存取消信号、provider round、
request sequence 和临时输出状态。Session 拥有长期数据，Agent runtime 只通过窄接口读写这些数据。

## 模块职责

| 模块 | 位置 | 负责内容 |
| --- | --- | --- |
| App | `src/main/app/` | 进程启动、退出、维护状态、组合依赖 |
| Desktop | `src/main/desktop/` | window、tab、tray、shortcut、floating、browser、renderer binding |
| Session | `src/main/session/` | Session 生命周期、Turn、查询、长期数据和删除规则 |
| Agent | `src/main/agent/` | Agent catalog、backend 选择、DeepChat/ACP instance 和执行 |
| Provider | `src/main/provider/` | Provider/model 配置、实例、请求和认证 |
| Tool | `src/main/tool/` | Tool catalog、执行、权限和本地 Agent tools |
| MCP | `src/main/mcp/` | MCP 配置、server/client 生命周期和 MCP 调用 |
| Skill | `src/main/skill/` | Skill 文件、扫描、同步、选择和贡献 |
| Plugin | `src/main/plugin/` | Plugin package、安装状态和能力登记 |
| Memory | `src/main/memory/` | 长期记忆、检索、写入、索引和后台维护 |
| Knowledge | `src/main/knowledge/` | 内置知识库、切片、索引和检索 |
| Workspace / File | `src/main/workspace/`、`src/main/file/` | Workspace 授权、文件树、搜索、转换和临时文件 |
| Remote | `src/main/remote/` | channel runtime、endpoint binding、远程命令和结果发送 |
| Scheduler | `src/main/scheduler/` | Cron job、run、delivery 和 detached Session |
| Settings | `src/main/settings/`、各模块 `settings.ts` | 底层设置存储和各模块自己的配置解释 |
| Data | `src/main/data/`、各模块 `data/` | SQLite 连接、schema，以及各模块自己的 table 访问 |

Desktop 内仍有 `WindowPresenter`、`TabPresenter` 等历史类名。它们只是 Desktop 模块内部的具体实现，
不是全局入口，也不能被业务模块用来查找其他能力。

Desktop 的平台合同：

- app-scoped 命令使用 application menu accelerator；`globalShortcut` 只用于真正的全局窗口显示/隐藏；
- primary app chrome 和列表行使用桌面 cursor 语义，内容 hyperlink 保留 link affordance；
- chat search、message jump 和 app chrome 默认使用 immediate/native scroll，不启用全局 smooth scroll；
- macOS window material 按 main/settings/window state 设置，Windows/Linux 保持各自平台选项；
- 修改 shortcut settings 后重新注册 menu accelerator，不创建第二套 renderer shortcut owner。

## 数据边界

`MainDatabase` 只负责连接、事务、schema、诊断、修复、备份和 reopen。业务 table 由各模块自己的
database 对象取得。长期运行对象不能缓存一次打开数据库时创建的旧 table；数据库维护完成后，
它们通过稳定的 database owner 读取当前连接。

通用 `SettingsStore` 和 `SecretStore` 只提供底层存储。Provider、MCP、Agent、Desktop、Sync、
Knowledge、Hook、Skill、Project 和 Upgrade 分别解释自己的配置，不通过一个通用 Config 业务入口。

## 通信边界

- Renderer 调用使用 `src/shared/contracts/` 中的 typed route。
- 各模块在自己的 `routes.ts` 创建 route map；App 统一注册并拒绝重名。
- 发给 renderer 的通知使用 typed event envelope。
- main 内部业务操作使用直接调用，不通过全局 event bus。
- route 只做通信适配；event 只表示已经发生的事实。

## 验证

模块行为由 typecheck、lint 和对应的 unit/integration tests 验证。Agent legacy boundary 仍由
`scripts/agent-cleanup-guard.mjs` 做窄范围检查；其余依赖方向在模块测试和 code review 中维护，不再运行
全仓库启发式扫描器。

详细合同见 [Agent 系统](./architecture/agent-system.md)、
[Session 管理](./architecture/session-management.md)、[Tool 系统](./architecture/tool-system.md)、
[Memory 系统](./architecture/memory-system.md)、[Tape 系统](./architecture/tape-system.md) 和
[事件系统](./architecture/event-system.md)。已完成的 main-process realignment 实施记录由 Git 历史保存。

---

### FLOWS (`docs/FLOWS.md`)

# DeepChat 当前核心流程

本文档描述 `2026-07-16` 的实际流程。旧 `Presenter`、通用 lifecycle hook 和全局 `EventBus`
不再属于当前流程。

## 1. main 进程启动

```mermaid
sequenceDiagram
    participant Entry as appMain.ts
    participant Main as app/mainProcess.ts
    participant DB as Database
    participant App as app/composition.ts
    participant Desktop as Desktop

    Entry->>Entry: single-instance / deeplink cache
    Entry->>Main: startMainProcess()
    Main->>Main: create splash and settings stores
    Main->>DB: unlock, open, migrate
    Main->>Main: migrate config storage and register protocols
    Main->>App: createMainProcessControl(dependencies)
    App->>App: create modules and connect narrow dependencies
    App->>App: register module route maps
    App->>Desktop: create first main window
    App->>App: start shortcut, tray, Scheduler and Memory maintenance
    App->>App: schedule deferred/background work
    App-->>Main: MainProcessControl
    Main->>Main: close splash
```

首个窗口之前必须完成数据库、配置迁移、route 注册和 ACP registry migration。Skill 扫描、MCP、
Remote、Provider warmup、legacy import 和统计回填在窗口可用后调度。

## 2. 创建 Session 并发送消息

```mermaid
sequenceDiagram
    participant R as Renderer / Remote / Scheduler
    participant Route as Module route or entry service
    participant Session as Session Lifecycle / Turn
    participant Manager as AgentManager
    participant Backend as DeepChat or ACP backend

    R->>Route: create or send
    Route->>Session: narrow operation
    Session->>Manager: resolve descriptor and session handle
    Manager->>Backend: choose by descriptor.kind
    Session->>Backend: initialize / send / cancel / snapshot
    Backend-->>R: persisted result and typed renderer event
```

Desktop、Remote 和 Scheduler 共用同一套 Session 生命周期。Scheduler 每次运行创建新的 detached
Session；Remote 保存自己的 endpoint binding；Desktop 只保存 renderer binding。

## 3. DeepChat 执行

```mermaid
flowchart TD
    Send["SessionTurn.send"] --> Instance["DeepChatAgentInstance"]
    Instance --> Prepare["读取 Session 数据并准备 prompt"]
    Prepare --> Run["创建独立 LoopRun"]
    Run --> Provider["ProviderRuntime.streamChat"]
    Provider --> Output["更新 message projection"]
    Output --> Tool{"有 Tool 调用?"}
    Tool -->|否| Settle["提交结果并结束 Turn"]
    Tool -->|是| Execute["ToolService 执行"]
    Execute --> Interaction{"需要用户交互?"}
    Interaction -->|是| Pause["保存交互并暂停"]
    Interaction -->|否| Tape["写入 Tape tool fact"]
    Tape --> Provider
    Pause --> Resume["最后一项完成后创建新的 resume Run"]
    Resume --> Provider
```

Provider、Tool、Skill、Memory 和 Session data 都通过创建时传入的必需接口使用。DeepChat runtime
不能从 App、Routes、Desktop、Remote 或 Scheduler 查找依赖。

- `generationSettings` 在 Session 创建、草稿和 active Session 中统一传递，包括 system prompt、
  temperature、topP、max tokens、reasoning effort 和 verbosity。
- `providerRoundCount` 按 outer round 递增，`requestSeq` 按实际 Provider attempt 递增；strict retry
  不会伪造新的 outer round。
- Memory prompt contribution 必须等待结果、清理内容、限制大小并允许失败；terminal extraction 在后台
  执行，并保持 epoch、cursor 和 fence 约束。
- `TapeToolFactWriter.appendToolFact` 在 message projection 完成后写 terminal tool call/result；
  写入失败不影响当前回复完成。

## 4. ACP 执行

```mermaid
flowchart LR
    Session["Session"] --> Manager["AgentManager"]
    Manager --> Kind{"descriptor.kind"}
    Kind -->|acp| Direct["Direct ACP backend"]
    Direct --> Runtime["AcpAgentRuntime"]
    Runtime --> Process["ACP process / protocol session"]
    Process --> Projection["Session message / Tape projection"]
    Kind -->|deepchat| Deep["DeepChat backend"]
```

Direct ACP 不进入 `DeepChatLoopEngine`。`kind=deepchat + providerId=acp` 仍是独立的兼容组合，
它使用 DeepChat loop，并把 ACP 当作 Provider。

## 5. Tool、MCP、Skill 和 Plugin

```mermaid
flowchart LR
    Agent["DeepChat runtime"] --> Tool["ToolService"]
    SessionSkills["Session selected Skill names"] --> Scope["Agent Skill scope"]
    AgentCatalog["Current Agent valid enabled catalog"] --> Scope
    Scope --> Skill["SkillService scoped snapshot"]
    Tool --> Local["Local Agent tools"]
    Tool --> MCP["McpService"]
    Tool --> Permission["Permission services"]
    Skill --> Tool
    Plugin["PluginService"] --> Skill
    Plugin --> MCP
```

Tool owns catalog assembly, permission checks, and execution routing. MCP owns server/client
lifecycles. Skill owns global packages and physical roots; Agents own logical bindings and their
independent enabled selections. A Run receives the closed intersection of persisted Session
selection and the current Agent's valid enabled catalog. Transfer, rebind, and Subagent entry
recompute that intersection. A missing manual Agent scope does not fall back to built-in
`deepchat`. Plugin registers package capabilities without owning MCP, Skill, or Tool runtime state.

Skill import explicitly selects a target Agent, previews the operation, and repeats validation in
Main before staging an independent package snapshot in the shared root and binding it to that
Agent. Source edits and deletion do not propagate into the imported package. See the
[shared Skills contract](./architecture/shared-skills/spec.md).

模型只能看到 `tape_search` 和 `tape_context`。Subagent 完成后，父 Session 保存指向 child Tape
固定 head 的 link；查询时通过明确的 linked Tape view 读取，不把 child entries 复制到父 Tape。
`subagent_orchestrator` 只在当前 Agent policy 开启且存在有效 slot 时提供，Subagent child 不能继续递归
创建 Subagent。

## 6. renderer binding

```mermaid
sequenceDiagram
    participant Window as Desktop window/tab
    participant Binding as DesktopSessionBinding
    participant Query as SessionQuery
    participant Runtime as Agent runtime

    Window->>Binding: activate(webContentsId, sessionId)
    Binding->>Query: read Session and messages
    Query-->>Window: projection
    Window->>Binding: deactivate or destroy
    Binding->>Binding: remove renderer binding only
    Note over Runtime: Session and running task are not deleted by window close
```

普通列表、历史和 binding 查询不会载入 Agent instance。只有执行、完整 restore 或明确的 backend
设置操作可以 hydrate runtime。

## 7. 数据库维护

```mermaid
sequenceDiagram
    participant Route as Sync / Database Security route
    participant App as App maintenance
    participant Entry as Remote / Scheduler
    participant Runtime as Session / Memory
    participant DB as MainDatabase

    Route->>App: runDatabaseMaintenance(operation)
    App->>Entry: stop new remote requests and scheduled runs
    App->>Runtime: fence Memory and suspend Session runtimes
    App->>DB: checkpoint and close
    App->>DB: import or encryption operation
    App->>DB: reopen
    App->>Runtime: resume Memory maintenance
    App->>Entry: restart Hook, Scheduler and Remote
```

维护期间新的 `chat.*`、`sessions.*`、`remoteControl.*` 和 `cronJobs.*` route 会被拒绝。
恢复失败时 App 进入 failed 并停止，不在关闭了一半的数据库上继续运行。

## 8. Remote

```mermaid
sequenceDiagram
    participant C as Remote channel
    participant R as RemoteService
    participant S as Session ports
    participant A as AgentManager
    participant D as DeliveryService

    C->>R: authenticated command / message
    R->>R: resolve endpoint binding and command
    R->>S: create, restore, send, cancel or interact
    S->>A: resolve typed backend
    A-->>S: stream / terminal projection
    S-->>R: typed result
    R->>D: render and deliver to channel
```

Remote 负责 channel runtime、endpoint binding、授权、命令解析和结果发送；它不拥有 Session 或 Agent
状态。`/agent` 只通过 Session assignment 选择可用 Agent。Feishu/Lark scan auth 的 begin/poll/cancel、
host 选择和 `open_id` pairing 保持在 Remote channel adapter 内，token 和 pairing secret 不进入 renderer
或聊天文本。Window 关闭不终止 Remote-bound Session。

## 9. Scheduler

Scheduler 查询到期 job 后，为每次 run 创建新的 detached regular Session：

```text
find due job
  -> acquire run identity and timeout
  -> create detached Session with saved Agent/settings/project
  -> SessionTurn.send
  -> wait for terminal result or cancel
  -> persist run status
  -> optional Remote delivery
  -> compute next run
```

Job、run、retry、timeout 和 delivery 由 `src/main/scheduler/` 负责。Scheduler 使用与 Desktop/Remote 相同
的 Session lifecycle，不直接构造 Agent runtime，也不复用上次 run 的 Session。Database maintenance 和
shutdown 会先停止接受新 run，再等待或取消已接收 run。

## 10. Sync 和导入

本地备份、数据库导入和 S3-compatible cloud sync 由 `src/main/sync/` 发起，并统一包在 App database
maintenance 中。Cloud flow 为：读取 SyncSettings 中的 endpoint/bucket/path/credential，生成或读取加密
数据库备份，上传/下载对象，校验完成后再替换本地数据。Secret 只保存在 main process 的 secret store，
renderer 只接收脱敏状态。

导入成功后重新打开数据库，各模块通过稳定 database owner 读取新 table；长期运行对象不能继续缓存
旧连接产生的 table。任何 close/import/reopen 失败都会让 App 进入 failed 并停止，不执行半恢复。

## 11. 退出

`before-quit` 先询问 Knowledge 是否允许退出。确认后，`MainProcessControl.stop()` 只运行一次，
并按明确顺序停止：

```text
cancel startup work
  -> stop Scheduler / Remote / Hook
  -> suspend Session runtimes
  -> stop Plugin / MCP / browser / Desktop resources
  -> stop Workspace / Skill / watcher / exec host
  -> fence and drain Memory
  -> stop Knowledge / Provider / ACP
  -> close SQLite
  -> destroy shortcut / notification / tray
```

更新安装复用同一条停止路径。数据重置和 App restart 也先完成停止，再由 Device 执行最终操作。

---

### Design System (`docs/design-system.md`)

# DeepChat Design System — CSS 令牌

> 从 `src/renderer/src/assets/style.css` 提取，基于 Tailwind CSS v4 `@theme inline` + CSS 自定义属性。
> 颜色详见「一~九」，排版 / 圆角 / 模糊 / 层级 / 动效 / 布局见「十~十五」，
> 系统化机制（色阶计算、Feedback、状态、浮层、密度、Token 契约）见「十六~二十一」。

---

## 一、核心色阶

### 1.1 Base 灰度（11 阶）

| Token | Light | HSL |
|-------|-------|-----|
| `base-50` | 纯白 | `hsl(0 0% 100%)` |
| `base-100` | 近白 | `hsl(0 0% 98.1%)` |
| `base-200` | 浅灰 | `hsl(0 0% 93.4%)` |
| `base-300` | 灰 | `hsl(0 0% 85.9%)` |
| `base-400` | 中灰 | `hsl(0 0% 67.1%)` |
| `base-500` | 半灰 | `hsl(0 0% 48.9%)` |
| `base-600` | 深灰 | `hsl(0 0% 36.5%)` |
| `base-700` | 暗灰 | `hsl(0 0% 28.7%)` |
| `base-800` | 深暗 | `hsl(0 0% 19.5%)` |
| `base-900` | 近黑 | `hsl(0 0% 13.2%)` |
| `base-950` | 极暗 | `hsl(0 0% 12.2%)` |
| `base-1000` | 纯黑 | `hsl(0 0% 1.2%)` |

### 1.2 Primary 蓝色（11 阶）

| Token | Hue | Lightness |
|-------|-----|-----------|
| `primary-50` | 208.6° | 94.3% |
| `primary-100` | 210° | 88.6% |
| `primary-200` | 210° | 80% |
| `primary-300` | 209.7° | 71.6% |
| `primary-400` | 209.8° | 62.9% |
| `primary-500` | 209.8° | 54.3% |
| `primary-600` *(主色)* | 209.9° | 42.9% |
| `primary-700` | 209.8° | 34.3% |
| `primary-800` | 209.6° | 27.8% |
| `primary-900` | 210° | 21.6% |
| `primary-950` | 209.6° | 15.1% |
| `primary-1000` | 210° | 8.6% |

### 1.3 Zinc 辅助灰度（10 阶）

| Token | HSL |
|-------|-----|
| `zinc-50` | `hsl(0 0% 98%)` |
| `zinc-100` | `hsl(240 4.8% 95.9%)` |
| `zinc-200` | `hsl(240 5.9% 90%)` |
| `zinc-300` | `hsl(240 4.9% 83.9%)` |
| `zinc-400` | `hsl(240 5.5% 64.3%)` |
| `zinc-500` | `hsl(240 4.2% 46.3%)` |
| `zinc-600` | `hsl(240 5.7% 34.1%)` |
| `zinc-700` | `hsl(240 5.3% 26.1%)` |
| `zinc-800` | `hsl(240 3.7% 15.9%)` |
| `zinc-900` | `hsl(240 5.9% 10%)` |
| `zinc-950` | `hsl(240 10% 3.9%)` |

---

## 二、Semantic surfaces

The desktop palette is shared by chat, settings, search, plugins and workspace panels. Components
consume these semantic values rather than defining local palettes.

| Token | Light | Dark | Purpose |
|---|---|---|---|
| `background` | `#ffffff` | `#191b1d` | Main reading canvas |
| `foreground` | `#24272b` | `#e6e9ed` | Primary text |
| `card` / `popover` | `#ffffff` | `#24272a` | Composer and floating surfaces |
| `card-foreground` / `popover-foreground` | `#24272b` | `#e6e9ed` | Surface text |
| `sidebar` / `container` | `#f3f4f5` | `#222426` | Navigation and window shell |
| `sidebar-foreground` | `#24272b` | `#e6e9ed` | Navigation text |
| `secondary` | `#f3f4f5` | `#2a2d32` | Secondary controls |
| `muted` | `#f3f4f5` | `#24272a` | Quiet content and user bubbles |
| `secondary-foreground` / `muted-foreground` | `#626973` | `#a0a7b1` | Readable secondary text |
| `accent` / `sidebar-accent` | `#e8ebef` | `#30363e` | Neutral selection and hover |
| `accent-foreground` / `sidebar-accent-foreground` | `#24272b` | `#e6e9ed` | Selected text |
| `border` / `sidebar-border` | `#e2e5e9` | `#343940` | Structural dividers |
| `input` | `#d5dae0` | `#414851` | Input boundaries |
| `primary` | `hsl(210 100% 43%)` | same | Blue primary actions; white foreground |
| `ring` | `primary-600` | `#62aaff` | Keyboard focus |

## 三、Custom semantic aliases

| Token | Light | Dark | Purpose |
|---|---|---|---|
| `bg-window-background` | `sidebar` | `sidebar` | Solid window background |
| `bg-card` | `card` | `card` | Surface alias |
| `bg-accent` | `accent` | `accent` | Interaction alias |
| `text-foreground` / `text-accent-foreground` | `#24272b` | `#e6e9ed` | Text aliases |
| `text-secondary-foreground` | `#626973` | `#a0a7b1` | Secondary text alias |
| `text-muted-foreground` | `#6b737e` | `#a0a7b1` | Quiet but readable text |
| `text-primary` | `hsl(210 100% 37%)` | `#62aaff` | Readable blue text |

Status labels and active controls consume `--text-primary` through
`text-[color:var(--text-primary)]`. Primary button fills use `--primary` with
`--primary-foreground`.

Explicit dark mode and the system-dark startup fallback use the same values. Semantic success,
warning, danger, notification and syntax colors retain their existing meanings. Green is not a
brand or navigation accent.

### 3.4 语法高亮

| Token | Light | Dark |
|-------|-------|------|
| `syntax-bold` | `hsl(227 76% 41%)` | `blue-500` |
| `syntax-comment` | `hsl(218 12% 64%)` | `gray-400` |
| `syntax-const` | `hsl(176 100% 24%)` | `emerald-300` |
| `syntax-property` | `hsl(221 97% 54%)` | `blue-100` |
| `syntax-symbol` | `hsl(0 0 15%)` | `hsl(0 0 100% / 0.8)` |
| `syntax-variable` | `blue-800` | `blue-300` |

---

## 四、通知色（Notifications）

| 类型 | Light 背景 | Light 文字 | Dark 背景 | Dark 文字 |
|------|-----------|-----------|----------|----------|
| **Success** | `hsl(142 48% 96%)` | `hsl(142 48% 28%)` | `hsl(142 22% 13%)` | `hsl(142 38% 72%)` |
| **Info** | `hsl(210 58% 96%)` | `hsl(210 52% 30%)` | `hsl(210 24% 14%)` | `hsl(210 42% 74%)` |
| **Warning** | `hsl(42 72% 95%)` | `hsl(38 58% 28%)` | `hsl(42 24% 13%)` | `hsl(42 48% 72%)` |
| **Error** | `hsl(0 58% 96%)` | `hsl(0 48% 34%)` | `hsl(0 24% 14%)` | `hsl(0 44% 75%)` |

---

## 五、用量指示色（Usage）

| Token | Light | Dark |
|-------|-------|------|
| `usage-low` | `hsl(142 71% 45%)` | `hsl(142 40% 60%)` |
| `usage-mid` | `hsl(48 96% 53%)` | `hsl(48 80% 60%)` |
| `usage-high` | `hsl(0 72% 51%)` | `hsl(0 70% 65%)` |

---

## 六、shadcn Theme 备用色系（`theme-*`）

| Token | 值 |
|-------|-----|
| `theme-background` | `white` |
| `theme-foreground` | `slate-950` |
| `theme-card` | `white` |
| `theme-card-foreground` | `slate-950` |
| `theme-popover` | `white` |
| `theme-popover-foreground` | `slate-950` |
| `theme-primary` | `blue-600` |
| `theme-primary-foreground` | `slate-50` |
| `theme-secondary` | `slate-100` |
| `theme-secondary-foreground` | `slate-900` |
| `theme-muted` | `slate-100` |
| `theme-muted-foreground` | `slate-500` |
| `theme-accent` | `slate-100` |
| `theme-accent-foreground` | `slate-900` |
| `theme-destructive` | `red-600` |
| `theme-destructive-foreground` | `slate-50` |
| `theme-border` | `slate-200` |
| `theme-input` | `slate-200` |
| `theme-ring` | `slate-950` |

---

## 七、完整 Tailwind 调色板

项目通过 `@theme inline` 注册了完整 Tailwind 色板（每色 50-950 共 11 阶），涵盖：

| 色系 | 说明 |
|------|------|
| `slate` | 蓝灰 |
| `gray` | 中性灰 |
| `zinc` | 暖灰 |
| `neutral` | 纯灰 |
| `stone` | 暖石色 |
| `red` | 红 |
| `orange` | 橙 |
| `amber` | 琥珀 |
| `yellow` | 黄 |
| `lime` | 青柠 |
| `green` | 绿 |
| `emerald` | 翡翠 |
| `teal` | 青 |
| `cyan` | 青蓝 |
| `sky` | 天蓝 |
| `blue` | 蓝 |
| `indigo` | 靛蓝 |
| `violet` | 紫罗兰 |
| `purple` | 紫 |
| `fuchsia` | 品红 |
| `pink` | 粉 |
| `rose` | 玫瑰 |

---

## 八、使用方式

### Tailwind 类名
```html

<div class="bg-background text-foreground">...</div>
<div class="bg-primary text-primary-foreground">...</div>
<div class="border-border">...</div>


<div class="bg-base-100 text-base-800">...</div>
<div class="bg-primary-600 text-primary-50">...</div>


<div class="bg-window-background text-text-foreground">...</div>
<div class="border-window-inner-border">...</div>
```

### CSS 自定义属性
```css
.my-component {
  background: var(--bg-card);
  color: var(--text-foreground);
  border: 1px solid var(--border);
}
```

---

## 九、设计决策记录

1. **Base 色阶** 使用 0% 饱和度灰度（纯中性），作为主背景体系。
2. **Primary 蓝色** 色相锁定在 ~210°，饱和度 72-100%，作为品牌主色。
3. **双主题体系并存**：shadcn 语义色（`background`/`foreground` 等）+ 自定义 Token（`bg-*`/`text-*` 等），自定义 Token 为实际组件使用的主要体系。
4. **Dark 模式** 通过 `.dark` / `[data-theme='dark']` 选择器触发，同时保留 `prefers-color-scheme` 媒体查询作为系统级兜底。
5. **Dark surfaces**: neutral charcoal distinguishes the canvas, navigation and raised surfaces. Hover and selection consume `accent`. Define both themes in the token layer; do not modify `src/shadcn` or hardcode component colors.
6. **通知色** 使用独立的 `dc-notification-*` 命名空间，与 shadcn 语义色解耦。
7. **语法高亮色** 在 Light/Dark 下分别使用硬编码 HSL 和 Tailwind 色板引用。

---

## 十、排版 Token

| Token | 值 | 用途 |
|-------|-----|------|
| `--dc-font-family` | `Geist, Noto Sans, ui-sans-serif, system-ui, sans-serif` | 正文字体栈（@font-face 内置 Geist） |
| `--dc-code-font-family` | `JetBrains Mono, Fira Code, Menlo, Monaco, Consolas, monospace` | 代码 / 等宽（`code pre kbd samp .font-mono`） |
| `--dc-font-scale` | `1`（`html.text-sm`=0.875 · base=1 · lg=1.125 · xl=1.25 · 2xl=1.5） | 全局字号缩放 |
| `--text-weight` | `400`（`.font-text`） | 正文默认字重 |
| `--display-weight` | `700`（`.font-display`） | 标题 / 强调字重 |

常用字号阶梯（Tailwind 默认）：

| 类 | 大小 | 行高 |
|----|------|------|
| `text-xs` | 12px | 16px |
| `text-sm` | 14px | 20px |
| `text-base` | 16px | 24px |
| `text-lg` | 18px | 28px |
| `text-xl` | 20px | 28px |
| `text-2xl` | 24px | 32px |

---

## 十一、圆角与模糊

### 圆角（语义级）

| Token | 计算 | 值 |
|-------|------|-----|
| `--radius-sm` | `calc(var(--radius) - 4px)` | 8px |
| `--radius-md` | `calc(var(--radius) - 2px)` | 10px |
| `--radius-lg` | `var(--radius)` | 12px |
| `--radius-xl` | `calc(var(--radius) + 4px)` | 16px |

> 基准 `--radius: 0.75rem`（12px）。控件用 sm/md，容器用 lg，浮层用 xl。

### 模糊（玻璃效果，三档语义值）

| Token | 值 | 工具类 | 适用 |
|-------|-----|--------|------|
| `--dc-blur-soft` | 8px | `.dc-blur-soft` | 状态浮条 |
| `--dc-blur-panel` | 16px | `.dc-blur-panel` | 按需使用的粘性浮层 |
| `--dc-blur-overlay` | 26px | `.dc-blur-overlay` | Agent 计划浮层 |

> 优先使用上述工具类，不要随手写 `backdrop-blur-*` 临时值。

---

## 十二、层级（z-index 阶梯）

| Token | 值 | 用途 |
|-------|-----|------|
| `--dc-z-base` | 0 | 页面内容 |
| `--dc-z-sticky` | 10 | 粘性顶栏、tab 栏 |
| `--dc-z-float` | 20 | 浮动元素、滚动 pill |
| `--dc-z-sidepanel` | 30 | 侧滑面板（全屏） |
| `--dc-z-popover` | 50 | Popover / Tooltip |
| `--dc-z-spotlight` | 90 | 系统级浮层 |
| `--dc-z-modal` | 100 | Dialog |
| `--dc-z-toast` | 1000 | Toast |

> 优先 `z-[var(--dc-z-*)]`；tooltip/popover 用 `--dc-z-popover`，禁止使用 `--dc-z-toast`。

---

## 十三、动效 Token

| Token | 值 | 用途 |
|-------|-----|------|
| `--dc-motion-fast` | 140ms | hover、轻量内容切换 |
| `--dc-motion-default` | 220ms | 面板开合、toggle |
| `--dc-motion-slow` | 320ms | 大型浮层出入 |
| `--dc-ease-out-express` | `cubic-bezier(0.16, 1, 0.3, 1)` | 快起→精确落点 |
| `--dc-ease-out-soft` | `cubic-bezier(0.22, 1, 0.36, 1)` | 柔和收尾 |

内置动画：`accordion-down/up`（fast + soft）、`collapsible-down/up`（default + express）、
`pulse`（2.4s 无限）、`loading-shimmer`（1.6s 线性）。

> `prefers-reduced-motion` 下全部动画压缩为 1ms 且仅执行一次；主题切换瞬间通过
> `.dc-theme-switching` 关闭所有过渡避免重绘卡顿。

---

## 十四、滚动条与阴影

### 滚动条（常驻导航线）

| 状态 | 浅色 | 深色 |
|------|------|------|
| 宽/形状 | 8px · `rounded-full` · 轨道透明 | 同左 |
| thumb | `rgba(107, 114, 128, 0.55)` | `rgba(148, 163, 184, 0.45)` |
| thumb hover | `rgba(148, 163, 184, 0.8)` | `rgba(148, 163, 184, 0.7)` |

### 阴影（最高只到 lg）

| 等级 | 用途 |
|------|------|
| `shadow-sm` | 卡片、下拉菜单 |
| `shadow-md` | Popover、菜单内容 |
| `shadow-lg` | Dialog（最高阴影等级） |

---

## 十五、布局与间距常量

| 断点 | 值 |
|------|-----|
| `sm` / `md` / `lg` / `xl` / `2xl` | 640 / 768 / 1024 / 1280 / 1536px |

| 间距 | 值 |
|------|-----|
| `spacing-0.5` / `1` / `2` | 2 / 4 / 8px |
| `spacing-3` / `4` / `5` / `6` | 12 / 16 / 20 / 24px |
| `spacing-8` / `10` / `12` | 32 / 40 / 48px |

| 页面常量 | 值 |
|----------|-----|
| Settings content | `max-w-5xl` (`SettingsPageShell`) |
| Chat content | `--dc-chat-content-width: 52rem`; shared by messages and composers |
| Expanded navigation | 264px; one sidebar with an agent dropdown |
| Collapsed navigation | 48px |
| 开关控件 | 32 × 18.4px（除以 `--dc-font-scale` 缩放） |

---

## 十六、色阶计算契约

### 16.1 唯一锚点

一切品牌色由**一个源色**派生：`primary-600 ≡ --primary ≡ hsl(210 100% 43%)`。

### 16.2 Primary 亮度阶梯

| 色阶 | 亮度 | 色阶 | 亮度 |
|------|------|------|------|
| 50 | 94.3% | 700 | 34.3% |
| 100 | 88.6% | 800 | 27.8% |
| 200 | 80% | 900 | 21.6% |
| 300 | 71.6% | 950 | 15.1% |
| 400 | 62.9% | 1000 | 8.6% |
| 500 | 54.3% | — | — |
| **600** | **42.9%（锚点）** | — | — |

### 16.3 派生规则

- **饱和度**：亮端 50–500 去饱和（50 阶约 72% → 600 阶 100%），暗端 600–1000 锁 100%。
  淡色端去饱和避免「粉色感」，暗色端保饱和维持品牌识别。
- **中性色**：`base-*` 饱和度恒 0，亮度 100% → 1.2%；`base-800`（19.5%）为深色边框参考点。
- **新增色阶**：必须按本阶梯插值（oklch 感知均匀插值优先），禁止手写无关色相或任意亮度。
- **不可派生场景**：`destructive`、`emerald`、`amber` 等语义色独立于品牌色阶，各自维护
  亮/暗两套，不并入 primary。

### 16.4 对比度验收

| 文本层级 | 最小对比度 | 适用范围 |
|----------|-----------|----------|
| 正文 | 4.5:1 | `foreground` / `text-foreground` 全部正文 |
| 辅助信息 | 3:1 | `muted-foreground`、时间戳、描述 |
| 状态 pill | 3:1 | pill 文字与 pill 底色 |
| 品牌蓝按钮 | 3:1 | 白字 on `primary`（43% 亮度 + 白字） |

验收时用相对亮度/oklch 计算，双主题分别核验；不满足则上移一个色阶，而不是微调透明度。

---

## 十七、Feedback 机制矩阵

### 17.1 四类反馈通道

| 通道 | 载体 | 特征 |
|------|------|------|
| 静态状态 | 状态 pill、徽标、开关 | 常驻，不打断 |
| 瞬时通知 | Toast | 自动消失，`aria-live` polite，不抢焦点 |
| 模态确认 | AlertDialog | 需用户确认，焦点陷阱 |
| 内联校验 | **UX 优先**（元素自身状态 + 防错设计），文字最后手段 | 就地表达，不打断流程 |

### 17.2 操作 × 通道映射

| 操作类型 | 乐观更新 | 成功反馈 | 失败反馈 | 不可逆确认 |
|----------|:---:|------|------|:---:|
| switch / toggle | ✓ 立即切换 | 状态自明（可选 toast） | 回滚 + destructive toast | — |
| 失焦自动保存 | 内联「保存中→已保存」 | 同上 | destructive toast + 保留内容 | — |
| 创建 / 编辑提交 | — | 关闭浮层 + 列表即时更新 | destructive toast + 保留表单内容 | — |
| 删除 | — | toast | destructive toast | **必选** AlertDialog |
| 覆盖导入 | — | 结果 toast（含新增/更新统计） | destructive toast（含失败原因） | 覆盖语义需确认 |

### 17.3 Toast 契约

- 时长：success 3s · destructive 5s
- 同屏 ≤ 3 条，可手动关闭，不抢焦点
- 含失败原因时使用描述行，不用标题堆叠长句

### 17.4 内联校验：UX 优先原则

错误发生在元素上，就用**元素自身的语言**表达，不要一上来就往旁边注入文字。优先级从高到低：

1. **预防（防错设计）**：约束输入本身——`maxLength`、类型/格式过滤、选择器替代自由输入、
   placeholder 给出正确示例、无效状态下禁用提交按钮。让错误不发生，而不是发生后解释。
2. **元素自身状态**：destructive 边框 / ring 变色、错误图标（icon-only + tooltip +
   `aria-label`）、焦点停留在出错字段、光标定位到问题位置。
3. **微动效**：出错瞬间轻量 shake（fast 140ms，一次），配合 ring 变化引起注意，不重复播放。
4. **文字说明（最后手段）**：仅当错误**无法从元素状态推断**（如格式规则、业务约束）时才
   注入一行短句，并用 `aria-describedby` 关联；说明写「如何修正」，不重复已可见的事实。

补充规则：

- 元素自身的错误状态必须自洽：用户在编辑该字段时错误态即时清除（不残留红色）。
- 错误恢复优先「就地修正」（光标定位、保留输入），避免让用户删除重输。
- 校验消息不堆叠：同一时刻每个字段最多一条说明。
- 旁注文字不是默认形态——评审时出现「每个输入框下面都挂一段提示」的界面即判定违规。

### 17.5 数据区四状态契约

任何数据驱动的区块必须显式覆盖四态，缺一不可：

| 状态 | 必备元素 |
|------|----------|
| loading | skeleton（与目标形状一致）或 spinner |
| empty | 对象说明 + 一个明确主行动 |
| error | 失败说明 + 重试入口 |
| disabled | 原因说明或可恢复的入口 |

---

## 十八、语义状态体系

统一状态集：`neutral / active / success / warning / danger / disabled`。

| 状态 | 色值来源 | pill 样式 | 文案时态 |
|------|----------|-----------|----------|
| neutral | `muted` + `muted-foreground` | 灰底灰字 | 名词（「本地」「导入」） |
| active | `primary` / `primary-10` 底 | 蓝底蓝字 | 名词（「已启用」「默认」） |
| success | `emerald` 系 | 绿底绿字 | 过去时（「已完成」「已同步」） |
| warning | `amber` 系 | 琥珀底琥珀字 | 现在时（「待处理」「注意」） |
| danger | `destructive` 系 | 红底红字 | 现在时（「失败」「不可用」） |
| disabled | `muted` + 低对比文字 | 灰底灰字 + 禁止光标 | 名词（「已禁用」） |

规则：

- 状态色只出现在**小面积**（图标、pill、细边框）；面积越大对比度要求越高，禁止大面积状态底色。
- 状态文案用**完成时/名词**表达事实，不用祈使句（「点击启用」属于按钮文案，不属于状态）。
- 状态色不承担装饰职能，同一信息不得同时用两个状态色表达。

---

## 十九、浮层与焦点契约

| z 层 | 交互契约 |
|------|----------|
| Popover / Tooltip（50） | Esc 或外部点击关闭；无焦点陷阱 |
| Dialog（100） | 焦点陷阱；关闭后焦点还原到触发元素 |
| Sheet（30，全屏侧滑） | 入场焦点移到首个可编辑字段；Esc 关闭；背景滚动锁定 |
| Toast（1000） | 不抢焦点；`aria-live` polite 播报 |
| Spotlight（90） | 焦点陷阱；系统级语义，覆盖全窗口 |

通用规则：

- 同一时刻只允许一个 true modal；AlertDialog 叠加在 Dialog 之上是唯一允许的嵌套。
- 浮层关闭后必须还原用户的上下文（滚动位置、焦点、选择状态）。
- z-index 一律走 `--dc-z-*` 阶梯，禁止绕过。

---

## 二十、密度与命中区契约

- 基础单位 4px（`spacing-0.5`=2px 为唯一例外）。
- 控件高度表：

| 控件 | 高度 |
|------|------|
| 按钮 sm / 默认 / lg | 32 / 36 / 40px |
| 输入框 / 选择器 | 36px |
| 图标按钮 | 28–32px |
| 开关 | 32 × 18.4px |
| 会话列表行 | 36–40px |
| 数据行（列表/表格） | 44–52px |

- 最小可点击区 ≥ 24×24px；icon-only 按钮不足时用 padding 或 wrapper 补足。
- 间距只用既有 spacing 阶梯，禁止中间值（如 13px、22px）。

---

## 二十一、新增 Token 契约

新增或修改设计令牌时，必须同时满足：

1. **颜色成对**：light / dark 两套值同时定义，不得只改一侧。
2. **标明用途**：每个 token 至少一个使用场景和禁用场景（写进注释）。
3. **对比度验收**：按 §16.4 通过双主题对比度检查。
4. **阶梯内取值**：motion 只用 140/220/320ms；radius 只用 8/10/12/16px；blur 只用 8/16/26px；
   z 只用 `--dc-z-*`；禁止新增中间档。
5. **禁止硬编码**：组件内不写裸色值（局部透明度叠加除外），一律引用语义 token。
6. **来源同步**：token 变更必须在 `src/renderer/src/assets/style.css` 与本文档同步落地。

---

## 二十二、dc-ui 设计组件层（`src/dc-ui/`）

与 `@shadcn` 同级的封装层（别名 `@dc-ui/*`），消费语义令牌与 shadcn 原语，
收敛 renderer 中的重复写法。组件以稳定交互语义分组并统一由 `@dc-ui/*` 导出。

**设计哲学：功能聚合、少而全。** 一个组件聚合一类交互的全部能力（如 `DcButton`
聚合 icon / tooltip / label / loading / active / 插槽），禁止为单一场景另立组件；
需要新能力时先扩展既有组件，再考虑新增。按钮体系收敛为一条链：
`DcButton`（唯一按钮）→ `DcSubmitButton`（表单提交语义）→ `DcFormActions`（按钮组）。

| 组件 | 导入路径 | 契约要点 |
|------|----------|----------|
| `DcButton` | `@dc-ui/components/button` | 统一按钮：variant/size 透传（`xs` 紧凑档 h-7 text-xs；`icon`/`icon-sm`/`icon-xs`/`icon-lg` 图标档 32/28/24/40px）；`icon`/`icon-size`（3/3.5/4）/`icon-class` 替代手写 `<Icon>`；显式 `tooltip` 内建悬浮提示，支持 `tooltip-side`/`tooltip-side-offset`/`tooltip-delay-duration`/`tooltip-content-class`/`tooltip-ignore-non-keyboard-focus`；`label` 仅提供可访问名（icon-only 至少 label/tooltip 其一，缺失 DEV 告警）；`loading` 内建 Spinner；`active` 高亮态；`$attrs`（data-testid 等）透传到按钮元素；默认插槽承接任意内容 |
| `DcStatusPill` | `@dc-ui/components/status-pill` | 六状态（neutral/active/success/warning/danger/disabled）+ 别名（running/loading/auth-required/auth-error/error/stopped/offline）；圆点 + 文案；`pulse`（loading）；`size` sm/xs |
| `DcConfirmDialog` | `@dc-ui/components/confirm-dialog` | AlertDialog 封装；`danger` 驱动 destructive 确认；`busy` 时禁用双按钮 + Spinner；`confirm-label`/`cancel-label` 默认走 `common.*` i18n；`confirm-attrs`/`cancel-attrs`/`busy-data-testid` 透传（保留测试钩子）；默认插槽放错误信息，`actions` 插槽自定义 footer。**confirm 不自动关窗**，`@confirm` 处理器负责关闭 |
| `DcToggleRow` | `@dc-ui/components/toggle-row` | label + Switch 行；有 `description` 时双行布局；`trailing` 插槽；`aria-label` 缺省用 label。Switch 必须用 `:model-value` + `@update:model-value`（reka-ui 2.x 约定，`checked`/`update:checked` 无效） |
| `DcEmpty` | `@dc-ui/components/empty` | shadcn Empty 封装；`icon`/`title`/`description` + `action` 插槽（空状态单一主行动） |
| `DcSkeleton` | `@dc-ui/components/skeleton` | `width`/`height`/`rounded` 参数化，`bg-muted/60` 基底 |
| `DcToast` | `@dc-ui/components/toast` | `notifyRenderer` 适配层；`success/info/warning/error({title, description?, code?})`；时长沿用 kind 策略，不新增 per-call duration |
| `DcTooltip` | `@dc-ui/components/tooltip` | 触发器 + 内容封装；`content`/`side`/`side-offset`/`disabled`/`delay-duration`；内建 Provider 200ms；**用于非按钮提示**（按钮提示一律走 `DcButton` 的 `tooltip` prop） |
| `DcSheetPanel` | `@dc-ui/components/sheet-panel` | 唯一标准 Sheet：`appearance="panel"` 为玻璃 header + ScrollArea 正文 + footer；`appearance="plain"` 兼容详情 Sheet 的默认 padding、`sm:max-w-xl`、无 ScrollArea 正文与 footer；`width-class`/`scroll-body` 保留精确布局控制 |
| `DcPopover` | `@dc-ui/components/popover` | 可交互 Popover：受控 `open`、`trigger`/`header`/`title`/`header-actions`/默认内容插槽；`width-class`/`align`/`side`/`side-offset`/`content-class`；默认 `w-80`、`align=end`、`overflow-hidden p-0` |
| `DcSectionCard` | `@dc-ui/components/section-card` | Settings section: rounded-xl, solid background, thin border, header/actions/description slots; class overrides are forwarded. |
| `DcInlineError` | `@dc-ui/components/inline-error` | UX 优先内联反馈：`error`（destructive + 图标）/`hint`（中性）二态，`role="alert"` |
| `DcForm` | `@dc-ui/components/form` | vee-validate `Form` 封装（shadcn form 原语）；内建 `useDcFormSubmit` 提交状态机，`@submit` 自动包裹 loading/success/error；`success-duration`/`error-duration`；`@success`/`@error` 事件 |
| `DcFormField` | `@dc-ui/components/form` | shadcn `FormField`+`FormItem` 家族封装：`name`/`label`/`description` + `#control="{ field }"` 插槽 + `FormMessage` 错误绑定（aria 关联） |
| `DcSubmitButton` | `@dc-ui/components/form` | 提交按钮（dc 的 DcButton 本体）：`status` 缺省自动注入最近的 `DcForm`；submitting→Spinner，success→✅（可配 `success-icon`/`success-label`），error→⚠，到时自动回退 idle |
| `useDcFormSubmit` | `@dc-ui/components/form` | 提交状态机 composable：`{ status, run, reset }`；`run(fn)` 驱动 idle→submitting→success/error，成功/失败按时长自动回退 |
| `useDcForm` | `@dc-ui/components/form` | 读取 `DcForm` 注入上下文（`DC_FORM_INJECTION_KEY`），供自定义提交控件使用 |
| `DcBadge` | `@dc-ui/components/badge` | shadcn Badge 语义化封装：default/secondary/outline/destructive 透传 + `success`/`warning`/`danger`/`active`/`neutral`（带 dark 变体，与 DcStatusPill 色系一致） |
| `DcCopyButton` | `@dc-ui/components/button` | 复制按钮：复用 `DcButton` 契约，`variant` 缺省 `ghost`；`copy-text` 必填触发复制（`useClipboard`），成功图标切 ✅（1200ms 自动回退、色值变 emerald）；`@copied`/`@error` 事件；`label`/`tooltip` 提供可访问名，`copy-text` 兜底 |
| `DcDropdownActionItem` | `@dc-ui/components/dropdown-action-item` | 菜单动作项：`icon`/`label`/`danger`（destructive 变体）/`disabled`/`inset` + `@select`，收敛 `Icon mr-2 + span` 散写 |
| `DcFormActions` | `@dc-ui/components/form-actions` | 表单底部操作组：「取消 + 提交（DcSubmitButton）」+ 中间插槽；`submit-status`/`cancel-label`/`submit-label`/`danger-submit` |

### 22.2 使用规则

- **按钮一律用 `DcButton`**：icon / tooltip / label / loading / active 均在组件内聚合，禁止手写 `Tooltip + Button + Icon` 组合；icon-only 至少传 `label` 或 `tooltip`（缺失 DEV 告警）。已有 tooltip 必须保留其文案、方位、延迟与显示条件；缺失 tooltip 的可操作 icon-only 控件必须补齐，优先复用对应操作的既有 i18n 文案，并同时作为 `label`。
- **选中/切换态按钮（toggle chip）切 `variant`，禁止 class 覆盖**：`outline` 变体自带
  `dark:bg-input/30 dark:border-input dark:hover:bg-input/50`，外部 class 传入的 `bg-primary` /
  `border-primary`（无 `dark:` 修饰符）在 tailwind-merge 中不与其冲突，暗色模式下会被压过，
  导致选中态失效（见 issue #2169）。正确写法：
  `:variant="selected ? 'default' : 'outline'"`，如需与 outline 保持等宽可补 `border border-primary`。
- **非按钮提示**（问号图标、下划线说明、Switch 包裹提示、Checkbox 行提示）不用按钮组件，用 `DcTooltip` / shadcn Tooltip 原样保持。
- **确认/删除类对话框统一 `DcConfirmDialog`**；动态多按钮弹窗、需隐藏确认按钮或 ESC 拦截的路由守卫弹窗保持原实现。
- **空状态统一 `DcEmpty`**（`#action` 放主行动按钮）；仅描述的内嵌小提示不套空态。
- **设置分区卡片统一 `DcSectionCard`**；danger-zone 等特殊边框语义卡不迁移。
- **状态展示统一 `DcStatusPill`**（标准六态 + 别名映射）；图表圆点/计数徽标不迁移。
- **紧凑内联错误统一 `DcInlineError`**（根元素 `role="alert"`）；带重试按钮的错误横幅保持原实现。
- **迁移必须行为等价**：保留原事件与修饰符（`@click` / `@select` / `.stop` / `.prevent`）、payload、禁用条件、`type`、loading 状态、Popover/Dialog 开闭、键盘与焦点行为、i18n key、`data-testid`；测试选择器（如 `[role="alert"]`）不因换组件失效。dc-ui 组件不得吞掉、改名或重发业务事件。
- **tooltip 补齐边界**：只为可操作的 icon-only 控件补齐；文字按钮、纯展示 Icon、以及说明型 Switch/Checkbox/链接提示维持原行为，不为统一形式强行增加 tooltip。
- 新增 dc-ui 组件需先证明既有组件无法覆盖；在本文档登记聚合后的完整契约，并符合 §20/§21 的密度与 token 规则。

---

### Release Flow (`docs/release-flow.md`)

# Release Flow

This document defines the maintainer release flow for DeepChat without rewriting existing `dev` / `main` history.

## Goals

- Keep `dev` as the only long-lived integration branch.
- Keep `main` as a stable mirror of reviewed release commits.
- Keep releases tag-driven through [`.github/workflows/release.yml`](../.github/workflows/release.yml).
- Avoid creating new merge commits on `main`.

## Branch Roles

- `dev`: active development and integration branch.
- `main`: stable mirror of released source snapshots.
- `release/<version>`: short-lived review branch cut from an existing commit on `dev`.

`release/<version>` must not carry release-only commits. If a release fix is required, land it on `dev` first and then move the release branch forward to the updated `dev` commit.

## Standard Release Sequence

1. Prepare release metadata on `dev`.

   - Update the version, `CHANGELOG.md`, and any release notes on `dev`.
   - Run the required local checks before cutting a release branch.

2. Cut the review branch from the release-ready commit on `dev`.

   ```bash
   git switch dev
   git pull --ff-only origin dev
   git switch -c release/v1.0.0-beta.4
   git push -u origin release/v1.0.0-beta.4
   ```

3. Open a PR from `release/<version>` to `main`.

   - The PR exists for review and CI only.
   - Do not use the GitHub merge button to land the PR.
   - Do not click "Update branch" on the PR, because it creates new merge commits.

4. If review finds a release issue, fix it on `dev` first.

   ```bash
   git switch dev
   git pull --ff-only origin dev
   # land the release fix on dev
   git branch -f release/v1.0.0-beta.4 origin/dev
   git switch release/v1.0.0-beta.4
   git push --force-with-lease origin release/v1.0.0-beta.4
   ```

   Use `--force-with-lease` only because the release branch is a disposable review branch that must stay identical to a commit already on `dev`.

5. After the PR is approved, fast-forward `main` locally on macOS or Linux.

   ```bash
   pnpm run release:ff -- release/v1.0.0-beta.4 --tag v1.0.0-beta.4
   ```

   The helper script validates:

   - the working tree is clean
   - the target release commit already exists on `origin/dev`
   - `origin/main` is an ancestor of the target commit
   - `main` can be updated with `git merge --ff-only`
   - the requested tag is free locally and on `origin`

   The `--tag` option validates tag availability and prints the tag command. Create and push the
   tag in the next step.

   Windows maintainers should skip this helper and use the manual release sequence below.

6. Create and push the release tag on the same commit.

   ```bash
   git tag v1.0.0-beta.4 release/v1.0.0-beta.4
   git push origin v1.0.0-beta.4
   ```

7. Wait for the tag-triggered Release workflow and review its draft.

   - Preflight must resolve the existing tag to the expected commit, confirm that commit is
     reachable from `origin/main`, match `package.json`, and find a non-empty matching CHANGELOG
     section before any native package starts.
   - All six native package jobs must pass. macOS x64 and ARM64 must be signed, notarized, stapled,
     and verified; Windows remains unsigned.
   - The workflow writes the draft only after fail-closed assembly and local revalidation, then
     verifies the uploaded assets through the GitHub API. Treat the run as successful only after
     that remote size and digest verification passes. Do not add ad hoc assets to the draft.
   - The draft must contain exactly nineteen assets: fourteen target files, four updater metadata
     files, and `release-index.json`.
   - If a release run must be retried after a native job has completed, rerun all jobs. Package
     manifests bind `GITHUB_RUN_ATTEMPT`, so mixing artifacts from different attempts intentionally
     fails assembly.

   Publish the verified draft manually after reviewing its notes, target list, and
   `release-index.json`.

8. Delete the temporary release branch after the release is published.

   ```bash
   git push origin --delete release/v1.0.0-beta.4
   git branch -d release/v1.0.0-beta.4
   ```

## Manual Release Sequence

Use this sequence when the automatic helper is unavailable, especially on Windows. It updates `origin/main` directly from the reviewed release commit and does not depend on the state of your local `main`.

1. Fetch the latest release refs.

   ```bash
   git fetch origin main dev --prune
   ```

2. Resolve the reviewed release commit and record it as `TARGET_SHA`.

   ```bash
   git rev-parse origin/release/v1.0.0-beta.4^{commit}
   # or
   git rev-parse release/v1.0.0-beta.4^{commit}
   # or
   git rev-parse <target-ref>^{commit}
   ```

3. Confirm the release commit already exists on `origin/dev`.

   ```bash
   git merge-base --is-ancestor <TARGET_SHA> origin/dev
   ```

4. Confirm `origin/main` can be fast-forwarded to the reviewed release commit.

   ```bash
   git merge-base --is-ancestor origin/main <TARGET_SHA>
   ```

5. Confirm the release tag does not already exist locally or on `origin`.

   ```bash
   git rev-parse --verify --quiet refs/tags/v1.0.0-beta.4
   git ls-remote --exit-code --tags origin refs/tags/v1.0.0-beta.4
   ```

   Both commands should report that the tag is missing before you continue.

6. Fast-forward `origin/main` directly to the reviewed release commit.

   ```bash
   git push origin <TARGET_SHA>:refs/heads/main
   ```

7. Create and push the release tag on the same commit.

   ```bash
   git tag v1.0.0-beta.4 <TARGET_SHA>
   git push origin refs/tags/v1.0.0-beta.4
   ```

8. Wait for the Release workflow, review the exact nineteen-asset draft using the checks in the
   standard sequence, and publish it manually.

9. Delete the temporary release branch after the release is published.

   ```bash
   git push origin --delete release/v1.0.0-beta.4
   git branch -d release/v1.0.0-beta.4
   ```

## Repository Settings

These settings are not stored in the repository and must be configured manually on GitHub:

- Enable **Require linear history** on `main`.
- Keep PR checks required for PRs targeting `main` and `dev`.
- Allow maintainers to push `main` only through the documented `ff-only` procedure.
- Treat the PR merge button for `main` as disabled by policy, even if the repository UI still shows it.

## CI Guardrails

- PRs targeting `main` must come from `release/<version>` branches.
- The head commit of a PR targeting `main` must already be contained in `origin/dev`.
- Release tags must point to commits that are already reachable from `origin/main`.

Build, Release, and package regression call one reusable workflow per operating system. The fixed
native runner and unpacked-directory mapping live in those called workflows, not in each caller.
Secrets are passed explicitly; package jobs have only `contents: read`, and only the final draft
publication job has `contents: write`.

Release assembly accepts exactly one distribution manifest for each of Windows, Linux, and macOS on
x64 and ARM64. It recomputes file digests, rejects incomplete checks or unknown files, and generates:

- `latest.yml` for Windows x64 and ARM64 NSIS payloads;
- `latest-mac.yml` for macOS x64 and ARM64 ZIP payloads, never DMGs;
- separate `latest-linux.yml` and `latest-linux-arm64.yml` AppImage metadata;
- `release-index.json` with six-target evidence and SHA-256 for the other eighteen public files.

Windows ARM64 uses the `build:win:arm64` script, installs only runtime payloads available for
`win32/arm64`, and packages only plugins whose manifest declares that target. Artifact names and
update metadata retain architecture so an ARM64 build cannot replace or be served as an x64
package. The equivalent platform/architecture contract for bundled plugins is documented in
[plugin packaging](./guides/plugin-packaging.md).

These rules are enforced in the repository workflows so the documented flow and the automation stay aligned.

## History Hygiene

Use first-parent history for day-to-day inspection:

```bash
git log --oneline --decorate --first-parent dev -n 30
git log --oneline --decorate --first-parent main -n 30
```

Avoid using `git log --all --decorate --graph` as the default project view because old release merges and stale branch refs make it noisier than the actual mainline history.

Clean up short-lived branches after they are merged:

```bash
git fetch --prune origin
git branch --merged dev
```

---

### Spec Driven Dev (`docs/spec-driven-dev.md`)

# Specification-Driven Development for DeepChat

## Core Philosophy

Specification-Driven Development (SDD) makes the specification the primary artifact. Specifications
do not serve code; code serves specifications. For substantial DeepChat work, the specification is
an RFC that defines the problem, required behavior, design decisions, invariants, and implementation
constraints before execution begins.

The spec is the durable source of truth. The plan is the single live execution tracker. Tests are
one validation mechanism and a form of regression protection; they do not drive implementation by
default. Use SDD for substantial work that needs shared context or a durable decision record, not
for every small edit. Keep spec → plan → code traceability without duplicating the same design or
status across files.

## Required Artifacts

Keep substantial active changes in a lightweight SDD folder so reviewers can find the intent without
hunting through code. Use one kebab-case folder per goal when SDD is needed:

- `docs/features/<goal>/` - new features, user-visible capabilities, integrations, and tools large
  enough to need a shared plan
- `docs/issues/<goal>/` - complex bug fixes, regressions, failing tests, CI failures, reliability
  issues, and prompt/runtime problems
- `docs/architecture/<goal>/` - refactors, migrations, dependency boundaries, shared contracts, runtime architecture, and cross-module design

Skip SDD unless a developer explicitly asks for it when the change is trivial or tightly localized:

- visual/style fixes, copy changes, and small UI layout adjustments
- simple localized logic changes with a clear owner module
- routine docs edits that do not change project direction

Pure release metadata work is exempt from SDD. Version bumps, `CHANGELOG.md` updates, release branch
management, tags, and release PR preparation should follow `docs/release-flow.md` without creating a
release-specific SDD folder.

Feature and architecture goals use two artifacts:

- `spec.md` - the RFC: context, goals, non-goals, design, invariants, interfaces, compatibility,
  acceptance criteria, and open questions
- `plan.md` - the ordered implementation steps and their live completion state, including final
  review, validation decisions, cleanup, and quality gates

Do not create `tasks.md`. The plan is the only execution tracker.

Complex bug goals normally use one file:

- `spec.md` - issue description, impact, root cause or suspected location, fix design, a concise
  implementation checklist, validation outcome, and linked GitHub issue if one exists

Add `plan.md` to a complex bug only when the implementation has multiple independently trackable
slices. Use the same artifact boundaries as feature and architecture work, and never add
`tasks.md`.

A bug is SDD-worthy only when the root cause, blast radius, or fix path is complex enough that
future developers benefit from the written record. For simple style defects or obvious local logic
fixes, skip `docs/issues/*` and implement directly.

If a bug fix introduces a new user-visible capability, data migration, public contract, or
cross-module redesign, classify the work as feature or architecture instead.

If a change is tiny, prefer skipping SDD over creating a token artifact.

## Artifact Responsibilities

### Specification

Write `spec.md` as the normative RFC. It should explain enough of the implementation direction that
a capable developer can make local coding decisions without inventing architecture. Include only
sections that carry real information, chosen from:

- context and problem
- goals and non-goals
- current and proposed design
- ownership, dependency direction, interfaces, and data flow
- invariants, failure behavior, compatibility, and migration
- security, privacy, and performance constraints
- acceptance criteria, open questions, and rejected alternatives

Acceptance criteria describe observable outcomes or independently verifiable contracts. They are
not a test-case inventory. Keep file-by-file work, status checkboxes, and command transcripts out of
the spec.

### Implementation Plan

Use `plan.md` as both the implementation plan and task tracker. Organize it into ordered checkbox
sections. Each step should be a coherent, reviewable implementation slice with its objective,
ownership boundary, essential implementation guidance, dependencies when any, and completion
condition.

Reference the spec instead of repeating its design. Do not split individual functions, files, or
tests into bookkeeping tasks unless they are independently meaningful deliverables. End the plan
with whole-change review, validation selection, temporary-verification cleanup, and required
quality gates.

## GitHub Issue Sync

Do not sync GitHub issues by default. Issue sync is a follow-up record, not a gate for local SDD or
implementation.

Only create or link a GitHub issue when the developer explicitly asks, or after asking and getting
approval once the SDD artifacts are written or the implementation is complete.

Eligible work:

- Complex bugs only; simple style defects and obvious local logic fixes should not get issues.
- Whole new features or major feature rewrites only; single actions, small behavior tweaks, and
  ordinary adjustments should not get issues.

If eligibility is unclear, ask the developer after the work is understood. Never self-authorize issue
creation just because local `gh` is installed and authenticated.

When approved:

- Feature work uses the `[feature]` label.
- Bug work uses the `[bug]` label.
- If the label is missing and `gh` has permission, create it.
- Record the issue URL or number in the SDD artifact.
- If `gh` is unavailable or unauthorized, continue local-only and note that no GitHub issue was
  created only when sync was requested or approved.

When opening a PR for linked work, include `Closes #NNN` in the PR body so GitHub closes the issue
after merge.

## Workflow

1. **Classification** - Decide whether SDD is needed, then choose feature, complex bug, or
   architecture.
2. **Specification** - Write the RFC, settle ownership and design, and resolve every open question
   that would change the implementation.
3. **Planning** - For feature, architecture, or multi-slice bug work, write one ordered
   implementation plan; do not create a separate task list or an upfront test matrix.
4. **Implementation** - Complete the planned code change while following existing DeepChat
   boundaries. Existing checks may run whenever they provide useful feedback.
5. **Review** - Review the complete change against the spec for hidden side effects, compatibility,
   failure behavior, performance, security, naming, and maintenance cost.
6. **Validation** - Decide which existing checks, temporary verification, and durable regression
   tests are warranted. Remove temporary verification before handoff.
7. **GitHub Sync** - Ask whether to sync an eligible GitHub issue only after the docs or
   implementation clarify the scope, unless the developer already requested issue sync.

Before implementation, inspect existing docs, code, callers, ownership, and nearby tests; choose the
correct SDD folder; and resolve every `[NEEDS CLARIFICATION]` marker. For architecture work that
changes or replaces a historical feature, update that feature's retained `spec.md` if it is still a
maintained contract.

### Implementation-First Validation

Implementation-first means finishing the planned implementation before deciding whether to author
new test code. It does not prohibit running existing tests, type checking, linting, builds, or
manual checks during development.

New test code before implementation is exceptional. Use it only when:

- the developer explicitly requests TDD;
- a minimal executable reproduction is required to understand a complex failure; or
- migration, concurrency, recovery, or protocol compatibility cannot be designed safely without
  characterization of existing behavior.

Record the exception and its reason in one sentence in `plan.md` or the complex-bug spec. Keep the
reproduction narrow; it may become a durable regression only if it protects a qualifying contract.

After implementation, classify validation as follows:

- **Existing validation**: run the smallest relevant existing tests and static or build checks.
- **Temporary verification**: add a probe, script, or test only to investigate implementation
  behavior, then remove it before handoff.
- **Durable regression protection**: commit the smallest test that protects user-visible behavior,
  a documented cross-module contract, persistence or migration, lifecycle or concurrency,
  recovery, a security boundary, or a proven regression.

Do not retain tests that merely mirror private control flow, assert incidental call order, duplicate
the implementation through mocks, or exist only to increase coverage. Prefer no new test to a
low-value implementation-coupled test.

Cleanup policy:

- Do not perform broad SDD cleanup during ordinary feature, bug, or architecture work.
- Use the `deepchat-sdd-cleanup` skill only when a developer explicitly asks to clean, prune, or
  organize SDD docs.
- Completed feature/architecture SDD content should become current documentation in `README.md`,
  `ARCHITECTURE.md`, `FLOWS.md`, `architecture/*.md`, or `guides/*.md`; remove `plan.md` and keep a
  spec-only folder when the RFC still defines a useful maintained contract.
- Completed issue folders may be deleted when a linked GitHub issue is closed or the implementation
  and validation evidence prove the bug no longer exists.
- Long-term history should be recovered from git history, not accumulated under `docs/archives/`.

## Six Core Principles

### 1. Specification-First Development

Write clear requirements, design decisions, invariants, and independently verifiable acceptance
criteria before writing code. Mark ambiguities with `[NEEDS CLARIFICATION]` and resolve them before
implementation. Include the architectural guidance needed to constrain the implementation, but
leave file-level sequencing and status tracking to the plan.

### 2. Architectural Consistency

Follow DeepChat's existing architectural patterns:

- **明确模块职责**: 把行为放到负责该能力的 main 模块，不新增通用 Presenter 总入口
- **Typed Event Communication**: main → renderer 状态通知使用
  `shared/contracts/events.ts` + `publishDeepchatEvent()`；main 内部操作使用直接调用
- **Secure IPC**: Prefer typed IPC via `src/preload/` (contextIsolation on); avoid ad-hoc channels
- **Type Definitions**: Shared types live in `src/shared/`

Every feature should integrate seamlessly with existing Presenters and use the established event
flow patterns.

renderer-main 能力使用 typed route / typed event + `renderer/api/*Client`。
`useLegacyPresenter()` 和 legacy presenter transport 已删除，不存在可复用的兼容路径。

### 3. Minimal Complexity

Start simple. Add complexity only when proven necessary. Avoid:

- Future-proofing (build for now, not hypothetical future needs)
- Unnecessary abstraction layers
- Over-generalization
- Premature optimization

Use framework features directly. Prefer a small coherent implementation slice that proves the
end-to-end design. If a change touches many files, explain why in the plan.

### 4. Compatibility & Migration

Prefer forward-looking designs, but treat stored user data, config, and external APIs as contracts. If a breaking change is necessary:

- Document the migration path in the spec/plan
- Include upgrade/rollback considerations (data, settings, UI defaults)
- Keep user impact explicit (what changes, what might break)

### 5. UI Consistency

Maintain consistency across the codebase:

- **Vue 3 Composition API** for all components
- **i18n** for all user-facing strings in `src/renderer/src/i18n/`
- **Tailwind CSS** following existing utility patterns
- Follow existing component conventions (props, emits, composition patterns)

### 6. Implementation-First, Risk-Based Validation

Put the primary reasoning budget into design and implementation. After the implementation is
coherent, choose the cheapest validation that can reveal meaningful failures. When durable
regression coverage is warranted, use Vitest in `test/main`, Vitest with Vue Test Utils in
`test/renderer`, and Playwright in `test/e2e`. Optimize for protected contracts and project
stability, not test count or coverage percentage.

## Development Checklist

### Specification Phase

- [ ] Problem, goals, and affected users or systems are clear
- [ ] Acceptance criteria are observable or independently verifiable
- [ ] Non-goals and constraints stated
- [ ] Ownership, interfaces, data flow, and required invariants defined
- [ ] Compatibility, migration, failure, security, and performance implications addressed
- [ ] Key UX states covered (loading/empty/error)
- [ ] No `[NEEDS CLARIFICATION]` markers remain

### Planning Phase

- [ ] Identify all involved owning modules and narrow ports
- [ ] Design event flow (if cross-process communication required)
- [ ] Define/verify IPC surface (`src/preload/`) and types (`src/shared/`)
- [ ] Define shared types in `src/shared/`
- [ ] Express the implementation as ordered, coherent slices in `plan.md`, or in the bounded
  complex-bug spec when no separate plan is needed
- [ ] Keep whole-change review and validation selection after implementation
- [ ] Identify risks (security/privacy/perf) and mitigations

### Implementation Phase

- [ ] Implement owning module and typed route/client changes
- [ ] Implement UI component (if needed)
- [ ] Add i18n keys (if user-facing)
- [ ] Review the complete implementation against the spec and affected boundaries
- [ ] Run existing validation and add temporary verification only where uncertainty remains
- [ ] Add the smallest durable regression tests only for qualifying behavior or contracts
- [ ] Remove temporary probes, scripts, and tests
- [ ] Run: `pnpm run format && pnpm run i18n && pnpm run lint && pnpm run typecheck`

## Common Patterns

```typescript
// 1. Typed Route / Client Method Signature
async methodName(params: InputType): Promise<OutputType>

// 2. Typed Event Publication (Main Process)
publishDeepchatEvent('settings.changed', payload)

// 3. Renderer-main Integration
const settingsClient = new SettingsClient()
await settingsClient.update([{ key: 'fontSizeLevel', value: 2 }])

// 4. Vue 3 Component Pattern
<script setup lang="ts">
import { SettingsClient } from '../../api/SettingsClient'

const settingsClient = new SettingsClient()
// Composition API logic
</script>
```

Compatibility note:

- 新 renderer-main 能力优先定义 `shared/contracts/*` 和 `renderer/api/*Client`
- `useLegacyPresenter()`、`presenter:call`、`remoteControlPresenter:call` 和
  `src/renderer/api/legacy/**` 已退休
- copy、file、openExternal 等低层能力通过 dedicated preload API 和 renderer client 封装
- `src/renderer/api/legacy/**` 保持删除，不恢复 legacy renderer-main boundary

## Quick Reference

- **Main modules**: `src/main/{app,session,agent,provider,tool,mcp,skill,plugin,memory,knowledge,workspace,file,desktop,platform}/**`
- **Renderer clients**: `src/renderer/api/**`
- **Tests**: `test/main/**/*`, `test/renderer/**/*`
- **Typed events**: `src/shared/contracts/events.ts`
- **Raw input constants**: `src/main/events.ts` and `src/renderer/src/events.ts`
- **IPC bridge**: `src/preload/`
- **i18n**: `src/renderer/src/i18n/`
- **Shared types**: `src/shared/types/` and `src/shared/contracts/`

## Definition of Done (DoD)

A change is “done” when:

- The acceptance criteria and documented invariants are met
- The complete implementation has been reviewed against affected boundaries and failure modes
- Relevant existing tests and required quality gates pass locally, or environment limits are
  reported precisely
- Any warranted durable regression tests are focused on qualifying behavior or contracts
- Temporary verification code has been removed
- User-facing strings use i18n keys
- Any migrations or breaking changes are documented
- Linked GitHub issues, when any, are referenced from the PR with `Closes #NNN`

---

### Architecture Agent System (`docs/architecture/agent-system.md`)

# Agent 系统

本文描述 `2026-08-07` 的当前合同。历史迁移方案、Presenter 拆分过程和已完成的分阶段
SDD 已删除；需要时从 Git 历史查询。

## 两种 Agent backend

`AgentManager` 只按 canonical `AgentDescriptor.kind` 选择 backend：

```text
kind=deepchat -> DeepChatAgentRuntime -> DeepChatAgentInstance -> DeepChatLoopEngine
kind=acp      -> AcpAgentRuntime      -> AcpAgentInstance      -> ACP process/protocol
```

`kind=deepchat + providerId=acp` 是兼容组合：它仍运行 DeepChat loop，只把 ACP 作为 provider。
Direct ACP 不进入 `DeepChatLoopEngine`，也不通过 DeepChat ToolService 执行外部 agent 自己的 loop。

内部 descriptor 使用 discriminated union。旧 `type` / `agentType` 只允许在 storage、route DTO 和
renderer compatibility adapter 出现；manager/backend 不做反射式 fallback。

## Agent 身份、默认选择与配置

所有 Agent 平级。`builtin`、默认选择和父子关系是不同概念：

- `deepchat` 是 `source=builtin` 的受保护第一方 Agent，保持启用、不可删除并允许重置；
- 默认 Agent 只表示入口未显式选择时使用哪个 Agent；硬编码 `deepchat` 只能用于这一兼容默认；
- 系统不存在主 Agent、父 Agent、`inheritsFrom` 或隐式模板关系；
- 每个 DeepChat Agent 只从自己的持久化配置和确定性的代码默认值解析 effective config；
- 应用级默认模型和自动压缩设置属于 app settings，不以 built-in Agent 配置充当别名；
- 修改 built-in Agent 不得触发其他 Agent 的配置、Memory 或 Skill fan-out。

升级迁移会先按旧规则（包括 built-in 配置和原有 fail-closed fallback）物化每个可读 manual
Agent 的 effective config，再启用独立解析；无法读取的配置也会尽量保留其旧 effective 值并记录
恢复证据。迁移完成后的独立 resolver 不再从 built-in Agent 静默补值，损坏配置在该阶段保持
fail-closed。

## 生命周期和所有权

```mermaid
flowchart TD
    Entry["Desktop / Remote / Scheduler / Subagent"] --> Session["Session Lifecycle / Turn"]
    Session --> Manager["AgentManager"]
    Manager --> Deep["DeepChat backend"]
    Manager --> ACP["ACP backend"]
    Deep --> Instance["one instance per loaded Session"]
    Instance --> Run["one LoopRun per provider/tool round sequence"]
    Run --> Provider["ProviderRuntime"]
    Run --> Tool["ToolService"]
    Run --> Memory["Memory ports"]
    Run --> Tape["Session Tape / transcript"]
```

- Session 拥有长期身份、settings、transcript、Tape 和 pending input。
- Each DeepChat Agent owns its configuration and logical Skill bindings. Mutable Skill packages,
  the catalog cache, and the watcher belong to the application-level Skills service. Sessions store
  only their Agent assignment and selected Skill names.
- 已载入 Session 在一个 backend 中最多有一个 instance。
- 每次执行创建独立 UUID Run，Run 拥有取消信号、logical round、request sequence、physical attempt 和
  临时输出；loop、resume 和 deferred tool execution 都不得复用旧 Run identity。
- Window、Remote endpoint 和 Cron run 只保存各自 binding，不拥有 Agent instance。
- 只读 list/query 不 hydrate backend；执行、完整 restore 或明确 backend 设置操作才允许 hydrate。

## DeepChat 执行合同

主要实现位于：

```text
src/main/agent/deepchat/
├── instance/   # per-session runtime state
├── loop/       # LoopRun, input/context coordination and ports
├── runtime/    # provider/tool loop, interaction, compaction and projection
├── memory/     # prompt contribution and terminal ingestion
└── resources/  # system prompt resources
```

必须保持的合同：

- runtime 只通过构造时注入的窄接口访问 Provider、Tool、Memory 和 Session data；不得反向读取 App、
  Desktop、Remote、Scheduler 或 route registry。
- `logicalRound` 统计一次模型响应及其工具结算循环；公共 `metadata.providerRounds` 和
  `maxProviderRounds` 继续保留原名称，但只映射该 logical-round 计数。上限在进入下一 round 前检查，
  resume 从已持久化 metadata 恢复计数。
- `requestSeq` 标识一份确定的 provider payload 和 ViewManifest；`physicalAttempt` 标识该 request 的
  实际发送次数。context recovery 改变 payload，因此推进 requestSeq 并把 physicalAttempt 重置为 1；
  同 payload 的 transient retry 保持 requestSeq，只推进 physicalAttempt。
- 每个 logical provider request 发送前都执行 context-pressure preflight。上一条成功 attempt 的
  provider-reported prompt usage 只在 provider/model/generation/tool schema/消息前缀完全一致时作为
  anchor，并只估新增 suffix；实际 fitted payload 与 continuation View 不一致或 usage 口径非法时不建立
  该 attempt 的新 anchor，无法匹配已有 anchor 时回退全量估算。preflight 不得只因启发式估算而丢弃
  system、当前 user input 或 tool protocol unit。
- Context compact 推进的是 Tape reconstruction boundary 和 provider View，不改写 raw Tape。semantic
  summary 失败时仍可提交 boundary-only anchor；semantic boundary recovery 只有在 durable cursor 前进且
  重新派生的 View 严格变小时才算 applied。已闭合的大 tool result 可独立在当前 View 中稳定 stub 化，
  原始 fact 保留在 Tape；先保护最新闭合 unit 的直接行动依据，较旧 unit 不足以解除压力时才允许压缩
  最新 unit。该路径以协议闭合和 projection 确实变化为进度，不冒充 boundary progress。
- Cache-aware v2 View 把当前 Tape incarnation 第一条 effective user fact 作为 authoritative protected
  prefix，位于 system 之后、checkpoint 之前；连续 cursor 仍可越过该 fact，View 每次从 raw Tape 重投影。
  pin 不使用 derivative 的 untrusted fence，不复制原文到 manifest，且只有 effective source entry、
  source-content hash、identity 与精确前缀 hash 全部匹配时才能跨 provider round 继承。
- 一次成功 provider response 重置 sequence-level context recovery latch；同一 Run 最多使用三条
  recovery sequence。恢复不能重放可能已执行副作用的原 user prompt，每个变化后的 payload 都必须写
  新 requestSeq 与 ViewManifest。
- 工具 operation identity 为 `(runId, requestSeq, providerToolCallId)`。`run_started` 必须先于 Run
  registration；dispatch fact 必须位于所有本地拒绝 gate 之后、真实副作用调用之前；known outcome
  必须先于 result projection；terminal fact 必须先于 transcript、status、hook 和 renderer terminal
  projection。任一 strict Journal commit 失败都阻止下游边界继续。
- `DeepChatContextCoordinator.streamProviderAttempts` 是 transient retry 的唯一 owner。每个 logical
  round 最多重试两次，使用可取消的指数退避并服从有上限的 `Retry-After`；context recovery 不消耗
  logical-round 或 transient-retry budget，每次实际 attempt 都重新进入 provider rate gate。
- 透明重放只允许发生在 `outputCommitted` 之前。首个投影的非控制事件会提交输出；usage、stop
  和 error 是控制事件，在 retry 决策完成前缓冲。无 stop 且无语义输出视为可重试的 premature
  EOF，已有 partial output 则保留输出并失败，绝不重放。
- message usage 对同一 logical round 内所有 physical attempt 的最终 usage snapshot 做 checked
  aggregation；Tape 仍逐 attempt 保存各自 usage。`retry_scheduled`、`retry_started` 和
  `retry_finished` observer 只用于结构化诊断，不进入 renderer event bus，也不是持久化事实。
- provider request trace context 必须按 physical attempt 创建并闭合捕获 identity，不能读取后续可变的
  Run counter。
- Run `AbortSignal` 必须贯穿 provider 建流和请求。AI SDK chat stream 显式使用 `maxRetries: 0`，由
  coordinator 统一重试；one-shot text/embedding 保持显式 SDK policy，ACP、image、video 和 TTS 不做
  DeepChat 透明重放。
- send input 在 Session 边界正规化为 canonical request；resume 不重新解释旧 route DTO。
- permission mode 属于 Session assignment/settings；一次 Run 使用开始时的闭合快照，deferred tool
  execution 仍需重新检查当前安全边界。
- Run 的 effective Skills 是 Session 持久化选择与当前 Agent 有效、启用 Skill catalog 的交集；不得
  回退到 built-in Agent catalog。
- paused interaction 按顺序结算；最后一项完成后创建新的 resume Run，不复用已经 settle 的 Run。
- paused terminal 只允许 permission/question action 保持 pending。任何其他 `pending` 或 `loading`
  block 都是 runtime invariant violation，必须在 paused terminal fact 之前转入 error settlement，不能
  静默改成 success。
- no-progress tool loop 由 `noProgressToolLoopGuard.ts` 终止；usage 由 runtime accumulator 跨 round
  累加。
- provider terminal reason 必须无损正规化；普通 `stop` 只有在确实解析出 tool call 时才能变为
  `tool_use`。

## ACP 执行合同

ACP 代码集中在 `src/main/agent/acp/`：

- `catalog/`：registry、migration 和 settings；
- `launch/`：安装与 launch spec；
- `client/`：connection 和 protocol session owner；
- `runtime/`：capability、process、session、filesystem、terminal、permission 和 persistence；
- `instance/`：per-session ACP state；
- `compatibility/`：允许保留的外部格式 adapter。

ACP capability 必须来自 initialize result，未声明能力失败关闭。filesystem 和 terminal 请求受
workspace/path guard 约束。取消、process exit、permission resolver 和 session persistence 都由 ACP
runtime 自己处理，不借用 DeepChat loop 状态。

## Subagent

Subagent 可用性只由当前 Agent delegation policy、正规化 slot 和 `sessionKind` 决定：

- 只有 regular DeepChat parent 且存在有效 slot 时才暴露 `subagent_orchestrator`；
- Subagent child 不能再次创建 Subagent；
- admission 后的 run 使用已固定的 task/slot snapshot，真正调用前仍重新检查 host policy；
- Each child uses an independent Session, workspace authorization, Tool mapping, Memory namespace,
  and permission state. Its Skill catalog is the global package set filtered by the destination
  Agent's bindings; it does not inherit the parent's bindings or runtime Skill state.
- 完成后父 Session 记录 child Tape 的 frozen-head link，不复制 child entries。

每个 Subagent run 有独立 deadline；默认 `300000ms`，允许 `1000-1800000ms`。一个 parent 最多同时
拥有三个 nonterminal run。deadline 或手动取消会标记未完成 task、请求 child cancellation，并记录
timeout/deadline/reason；run 的终止不能无限等待被阻塞的 child cleanup。Child handoff 固定包含
`Result`、`Evidence`、`Changed Files`、`Validation`、`Unresolved`，调用方的 `expectedOutput` 只能追加
指导，不能替换基本合同。

## 删除与 transfer

Deletion does not depend on a currently valid Agent descriptor. It clears both backends' cached and
durable bindings, then Session data, permissions, Skill selections, Agent Skill bindings, and the
app-session row. Deleting an Agent preserves globally stored Skill packages and other Agents'
bindings. ACP and DeepChat transfer validate the destination, commit ownership, and filter Session
Skill selections through the destination Agent's assigned catalog before closing the old backend.
Failure preserves the original assignment.

DeepChat Agent 删除与 Session create、detached create、Subagent create、transfer、fork 共享按 Agent ID
的 lifecycle gate。删除先阻止新的 assignment，再等待已进入的 assignment 完成，最后重新检查持久化
Session 引用；不得留下指向已删除 Agent row 的 Session。

## 防回归

`scripts/agent-cleanup-guard.mjs` prevents retired Agent/Session Presenter paths and imports from
returning. Unit and integration tests protect configuration materialization and independent
resolution, Agent Skill binding isolation, Session/catalog intersection, backend behavior, Subagents,
and Tape. There is no repository-wide heuristic architecture guard.

`pnpm run test:agent:eval` 提供离线 deterministic Agent 行为基线，使用 scripted provider/tool 直接
覆盖 production loop。场景包括 direct completion、多轮 tool、tool failure、permission pause、cancel、
pending yield、round guard、no-progress、empty output 和 context/provider errors；断言 persisted run
metadata、usage/cache fields、provider/tool budgets，不调用真实 provider，也不写仓库 artifact。

关键入口：

1. `src/main/agent/manager/agentManager.ts`
2. `src/main/session/turn.ts`
3. `src/main/agent/deepchat/instance/`
4. `src/main/agent/deepchat/loop/`
5. `src/main/agent/deepchat/runtime/`
6. `src/main/agent/acp/instance/`
7. `src/main/agent/acp/runtime/`
8. `test/main/agent/`

## ACP extension state and interaction ownership

Direct ACP sessions negotiate Lody capabilities per process connection. `AcpElicitationBridge` owns
live forms and URL consent; typed routes expose opaque request IDs to the question dock and global
dialog. Answers are ephemeral and bypass ordinary transcript messages. `AcpSessionController` owns
idle extension state, monotonic accounting, remote task observations and isolated child stream
mapping. Existing ACP metadata stores snapshots, with source identity and serialized merge guards.

The built-in `update_plan` tool and live ACP plan capability are retired. Plan notifications are
ignored; historical message compatibility does not restore runtime plan state. See the
[plan retirement contract](./agent-plan-retirement/spec.md) for the retained data and display boundary.

`AcpAgentInstance` retains the prompt slot for goal execution, native request steering and idle history
or fork operations. No remote task creates a local loop. The parent projection cannot consume child
output or history replay as live generation. Remote fork imports the target's rewritten anchors and
records inherited usage independently. See the [Lody extension contract](../features/acp-lody-extensions/spec.md)
for supported controls, compatibility gates and bounds.

---

### Architecture Event System (`docs/architecture/event-system.md`)

# 事件系统

本文说明 DeepChat 当前的 main → renderer 通知边界。全局 main 进程 `EventBus` 已经删除。

## 当前路径

```text
负责业务状态的模块
  -> App composition 注入的 publishDeepchatEvent(name, payload)
  -> shared/contracts/events 检查数据
  -> WindowPresenter 发送 deepchat:event
  -> preload createBridge 分发
  -> renderer/api client 或 store 处理
```

| 层 | 文件 | 职责 |
| --- | --- | --- |
| 事件定义 | `src/shared/contracts/events.ts` | 汇总 renderer 可见事件及数据类型 |
| 通道名 | `src/shared/contracts/channels.ts` | 定义 `deepchat:event` |
| 发布入口 | `src/main/app/composition.ts` | 创建 event envelope 并把发送函数注入模块 |
| 发送 | `src/main/desktop/window/` | 发给全部窗口或指定 webContents |
| preload | `src/preload/createBridge.ts` | 接收统一通道并按事件名分发 |
| renderer | `src/renderer/api/*Client.ts` 和 store | 注册监听并负责清理 |

## Renderer 可见事件

`DEEPCHAT_EVENT_CATALOG` 是唯一事件表。新增 renderer 可见事件时：

1. 在 `src/shared/contracts/events/*.events.ts` 定义名称和数据；
2. 从 `src/shared/contracts/events.ts` 导出；
3. 通过 composition 注入的 publish function 发布；
4. renderer 通过 `window.deepchat.on()` 或对应 client 接收。

示例：

```ts
publishDeepchatEvent('chat.stream.completed', {
  eventId,
  userStop: false
})
```

```ts
const stop = window.deepchat.on('chat.stream.completed', (payload) => {
  messageStore.finishStream(payload.eventId)
})
```

## Main 进程内部通信

main 内部不再有全局 `EventBus`。根据用途选择明确方式：

- 要求另一个模块执行操作、等待结果或保证顺序：直接调用窄接口；
- 表示某个模块内已经发生的事实，并且确实有多个观察者：由该模块提供有类型的订阅接口；
- 表示 ready：提供可查询状态或可等待的 Promise；
- 没有接收方或与 renderer typed event 重复：删除。

Provider DB 更新是当前保留的模块内通知：Loader 通知模型能力索引重建，App 把更新明确连接到
Provider 的后台 model 刷新。它不是通用事件总线。

`src/main/events.ts` 只保留少量明确的原始窗口输入常量，例如设置导航、开发入口、deeplink 和快捷键。
这些常量不能用于 main 模块之间传递业务命令。

## 请求和返回

renderer 发起查询或命令时使用 typed route：

```text
Vue component/store
  -> renderer/api client
  -> window.deepchat.invoke(routeName, input)
  -> shared/contracts/routes 检查输入和输出
  -> src/main/routes handler/service
  -> 负责该行为的模块
```

| 需要 | 边界 |
| --- | --- |
| 查询数据或执行命令 | typed route |
| 通知 renderer 状态已变化 | typed event |
| main 模块要求另一个模块做事 | 直接调用窄接口 |
| 发给单个 webContents | 使用 Desktop 注入的窄发送函数 |

## 自动限制

- 全局 `EventBus`、`eventBus`、`sendToMain()` 和 `PROVIDER_DB_EVENTS` 保持删除；
- renderer 可见数据必须通过 shared typed event；
- 原始 IPC 和宽泛 `window.electron` 访问只能留在明确的 preload/bridge 边界；
- `useLegacyPresenter()`、`presenter:call`、`remoteControlPresenter:call` 和
  `src/renderer/api/legacy/**` 保持删除。

## 相关文档

- [架构总览](../ARCHITECTURE.md)
- [核心流程](../FLOWS.md)
- [Agent 系统](./agent-system.md)
- [Tool 系统](./tool-system.md)

---

### Architecture Memory System (`docs/architecture/memory-system.md`)

# Memory 系统

本文是 Agent Memory 的当前架构合同。已完成的 correctness、privacy、performance、state model 和
domain convergence SDD 已合并到这里。

Agent Memory 本身不使用 V1/V2/V3 作为产品或架构版本。文中的 `v1`、`v2` 仅表示 DuckDB
vector-store 文件格式。

## 所有权

```mermaid
flowchart LR
    Agent["DeepChat runtime"] --> Prompt["MemoryPromptContributor"]
    Agent --> Ingest["MemoryIngestionObserver"]
    Prompt --> Memory["MemoryService"]
    Ingest --> Memory
    Memory --> Core["claims / temporal / scope / policy"]
    Memory --> Services["retrieval / write / directives / maintenance"]
    Services --> Claims["authoritative claims"]
    Services --> Trust["trusted directives"]
    Services --> Derived["lineage / tombstones / dirty work"]
    Services --> Projection["working / FTS / vector projections"]
    Services --> Provider["embedding / text provider gateway"]
```

- `src/main/memory/` 唯一负责长期记忆、检索、写入、persona、向量索引和后台维护。
- `src/main/agent/deepchat/memory/` 只负责每个 Session 的 prompt contribution、terminal ingestion、
  epoch、cursor 和 fence。
- Session 保存 Memory cursor/settings，不拥有 Memory row 或 vector store。
- App 负责 shutdown/database maintenance 时的全局 fence 和停止顺序，不解释 Memory 业务状态。
- Memory runtime 通过 `TapeRawEntryReader` 和 `TapeAnchorWriter` 读取执行事实、记录
  `memory/view_assembled` 与 `memory/extract` anchor；Memory routes 只通过 `TapeInspectionReader`
  获取 effective source span 和 manifest DTO，不接收 Tape table 或 raw Tape row。

## 数据与状态

`agent_memory` 中的原子 claim 是 remembered fact 的唯一权威来源。working row、FTS mirror、DuckDB
vector sidecar 和 renderer summary 都是可重建 projection，不能反向成为事实源。

| 数据 | 语义 | 生命周期 |
| --- | --- | --- |
| `agent_memory` | 原子 claim、事实置信度、时间有效性、来源、适用 scope | 权威 |
| `agent_memory_directive` | 经用户显式创建或批准的可执行指令 | 独立 trust plane |
| `agent_memory_derivation` | claim-to-claim 持久 lineage | 权威关系 |
| `agent_memory_tombstone` | 精确遗忘的 hash-only suppression identity | 随 Agent namespace 保留 |
| `agent_memory_dirty` | 增量 consolidation 的有界 work index | 可重建派生状态 |
| `agent_memory_clear_job` | Agent claim clear 的持久 fence、进度与恢复阶段 | vector cleanup 后删除 |
| working / FTS / vector | 注入、关键词和相似度 projection | 可删除并重建 |
| audit | 运维可观测事件 | 有 retention，不承担 lineage |

Memory domain 使用明确的 lifecycle、embedding state、temporal metadata、scope 和 execution
identity，不能把多个状态重新压回一个含混枚举。所有写入带 Agent namespace；跨 Agent、跨 scope 或
stale epoch 的结果不得提交。

核心约束：

- working、episodic、semantic/persona 数据保留各自语义和去重规则；
- claim 的 `confidence` 只表示事实证据置信度；`temporal_confidence` 独立表示时间解析置信度，两者
  不得共用更新规则；
- temporal interval 使用 `[valid_from, valid_until)`；precision 和 IANA timezone 显式持久化；
- provenance key 使用 Agent、kind、scope 和 canonical content 构造；Agent scope 保留 legacy v2
  identity，legacy key 只在读取/迁移边界兼容；
- `agent_id` 是 storage/security owner；`agent|user|project|session` scope 只控制 owner 内的
  applicability，缺少窄 scope context 时不得放宽；
- 历史 `user_scope` 只作为兼容 shadow；迁移后的历史 row 保持 Agent scope，新的 User-scope write
  才同步 shadow；
- 当前 Agent scope 是默认且完整的产品路径；Session scope 只在 recall 收到当前 Session ID 时生效，
  自动 extraction 仍写 Agent scope；User/Project 的存储、类型和显式 route 是 internal/experimental
  能力，普通 chat runtime 尚无权威 identity 接线；
- 同一配置 epoch 内的异步 extraction/embedding 才能提交，ABA 配置切换由 execution identity fence
  拒绝；
- provider/model/dimension identity 与 vector store metadata 必须一致，不一致进入 reindex/quarantine；
- renderer DTO、tool contract 和公开 status 由 route adapter 正规化，不泄漏内部 provider secret。
- 业务时间通过 `MemoryDomainClock` 注入；timeout、lease 和 performance measurement 继续使用各自的
  infrastructure clock。

## 读取路径

```text
turn preparation
  -> MemoryPromptContributor
  -> retrieval soft deadline
  -> owner + scope candidate filtering
  -> directive suppression
  -> temporal eligibility / scoring / deduplication
  -> one bounded contribution allocator
  -> separate memory and directive user-role contributions
  -> canonical send context
```

Memory contribution 必须等待到 soft deadline，成功时限制 token/字符大小并清理注入内容；失败或超时
允许当前消息继续。查询不能无限等待 native vector store 或 provider。Memory 只返回 contribution
文本、selection manifest 与成功持久化的 `memory/view_assembled` anchor ID；不能接收或重写 base
system prompt。

Query embedding 只送用户消息的前 2000 个 code point，deadline 由 provider gateway 单独持有：按同一
provider/model 最近 warm-up 与 query 调用的平滑耗时乘以 headroom，夹在 800ms 下限与 2s 上限之间；
一次 deadline miss 让下一次尝试放宽到上限，成功后回到观测值。Retrieval 不再叠加第二个 soft deadline，
gateway 的 deadline 错误在 degradation 中归类为 `embeddingTimeout`。

Warm recall 的 query embedding 按 Agent 与当前 provider/model identity 使用进程内有界熔断：短窗口内
连续 deadline/transport failure 会临时跳过 vector path 并直接使用已生成的 FTS candidates；冷却后只
允许一个 half-open probe，成功自动恢复。取消和本地 capacity rejection 不计 provider health failure；
配置切换、Agent cleanup 与 presenter disposal 清除旧状态。熔断不应用于 embedding batch、warmup、
dimension discovery 或 text generation，也不改变健康路径的 scoring。

FTS 在 SQL `LIMIT` 前应用 Agent 和 scope predicate。Vector store 仍按 Agent namespace 查询，使用
有上限的 oversampling，并在 ranking 前通过 SQLite authoritative row 重新校验 owner、scope、
lifecycle、revision 和 embedding identity；不得依赖 vector candidate 本身做授权判断。过滤后不足
top-K 时，FTS/vector 复用同一 query embedding 做几何增长的 adaptive refill，每个 source 最多
800 candidates；到达上限仍不足时记录 `candidateBudgetExhausted`，不得进入无界 loop。

current recall/injection 会排除高置信度的过期或尚未生效 state；低置信度时间解析 fail-open，但降低
权重并附带 qualification。Event 保留为历史 evidence；Plan 即使过期也只能表述为 previously
planned；Recurring 使用封闭 temporal kind 和已知 recurrence window。Decision retrieval 使用
evidence 视角，不能把时间过滤误当作物理删除。

Active `suppress_topic` directive 在 access accounting 前过滤 recall candidate。普通 memory、
persona 和 working projection 进入只读 `<context-data>` 容器，内容严格作为 data；Active directive
进入独立 typed contribution。抽取结果只能创建 draft directive，只有用户显式创建或 approve
操作能让 directive active。CJK topic 使用标准化 substring 匹配，因此至少需要两个可见 base
character；写入端拒绝过宽 topic，运行时也忽略历史脏值。

Active directive 的当前 contribution 顺序是 `updated_at DESC, id ASC`，表示确定性的最近更新顺序，
不表示 privacy、style 或其他语义优先级。预算 manifest 会记录 dropped IDs，但 active persistence
不保证每轮都能装入 prompt。`priority`/`pinned` 需要独立的产品语义、迁移和用户控件，不能从文本或
source 猜测。

一个纯 allocator 管理总 memory contribution budget：directive ceiling、persona/working
floor/ceiling、query-recall reservation，以及未使用份额的有界 borrowing。最终 assembler 仍执行
hard ceiling，并在 manifest 中记录 allocation。

普通 send 把 contribution 前置到当前 user message，原始用户指令保持在同一 message 的末端。resume
把 contribution 注入目标 assistant 所属 turn 的 user message；找不到 owner 时 fail-open 省略，不能在
partial assistant 后新增 user。tool/skill refresh 与 context pressure recovery 必须复用本 turn 已生成的
contribution，不能重复 retrieval、access accounting 或 anchor append。Memory、summary 与 handoff
state 都属于 untrusted conversation data，不得提升为 system role。

Vector store v2 使用 `<agentId>.v2.duckdb`、plain `FLOAT[]` table 和 exact scan，不在 hot path 加载
持久化 HNSW/VSS。v1 文件只通过隔离 reader 做一次性迁移，staging rename 是 publish commit point。
详细迁移窗口和后续 VSS removal 任务保留在
[memory-vector-store-v2](./memory-vector-store-v2/)。

## 写入路径

```text
terminal turn projection
  -> read bounded ingestion projection range
  -> rebuild from effective Tape or fall back when projection is stale/unavailable
  -> collect bounded text chunks
  -> extraction with domain-clock context
  -> normalize temporal claim + typed scope
  -> scoped provenance / tombstone / conflict checks
  -> claim + lineage + dirty-work transaction
  -> embedding pipeline / vector upsert
  -> advance cursor only after owned work settles
```

- terminal extraction 在后台运行，不延迟已完成回复；
- Subagent 会话（`sessionKind: 'subagent'`）仍接收其 Agent 的 memory injection，但不进入 terminal 或
  compaction extraction：子会话的 "user" turn 是 parent Agent 写下的任务描述，抽取会把 parent 的指令
  当作用户事实；子任务的结论由 parent 会话从 parent Agent 的回复中抽取；
- A fork keeps native `message/<role>` facts for its cloned messages. Its Memory cursor maps only
  the source's successfully extracted prefix onto the densely renumbered clone, excluding failed
  messages and compaction markers. Later terminal turns extract the unprocessed cloned tail and
  new messages even if the source never resumes, without replaying the processed prefix.
  Cursor seeding fences older extraction work. Target delete/edit/retry retains the existing
  `invalidateFromOrderSeq` behavior, rewinding the cursor and extracting again from that point;
- malformed temporal metadata 只拒绝该 candidate，不让它变成永久事实，也不让整个 extraction batch
  失败；
- startup 发现 legacy/corrupt external claim 的非法 temporal metadata 时，先归一化字段并将 claim
  archive；不得把损坏状态提升成可召回的永久 atemporal fact。Persona/working 则归一化到其强制
  atemporal 形式；
- Startup repairs invalid scope pairs before asserting integrity, without widening applicability.
  Agent rows drop stray `scope_id` values; User rows resync the shadow from a valid `scope_id`, or
  recover a missing or malformed ID from a valid shadow; Project/Session rows drop stray shadows.
  Narrow-scope rows with unrecoverable identities, including persona/working rows, are deleted
  with a warning and an FTS rebuild instead of being promoted to Agent scope. Before deleting a
  row covered by a pending clear, persist its recoverable provenance tombstone and increment
  the job's removed count in the same transaction, using the clear job's timestamp. Count every
  deleted row covered by the clear, including rows without provenance. An unknown scope cannot
  produce a content tombstone or widen suppression. Both temporal and scope repairs suspend the
  clear-job guard within their transaction and restore it before returning, so pending clears cannot block
  startup repairs while ordinary domain writes remain fenced;
- 同 content 在不同 scope 可独立存在；update、supersede、conflict 和 merge 不得跨 scope；
- exact tombstone lookup 与 insert 位于同一 transaction，关闭 delete/re-extraction race；
- model 发起的 `memory_remember` 不是用户重新授权，不得释放 tombstone；只有 renderer 中的显式
  user-add action 可以原子地重新写入完全相同的 forgotten claim；
- model-derived directive suggestion 只进入 draft，不得经 claim extraction 通道直接 active；
- cancellation signal 贯穿 text provider、embedding provider 和 vector query；
- write coordinator 对同一 Agent 的配置变化、重建和 maintenance 串行化；
- stale result、partial batch 和 provider cancellation 有明确 terminal outcome；
- vector store 异常进入 typed error/quarantine，不得把消息发送永久挂起。

## Tape 与 ingestion projection 边界

`DeepChatMemoryIngestionProjectionTable.readCurrentRange` 在一条只读 SQL 中同时观察 Tape head 和
projection head。这是明确的基础设施例外：拆成两次查询会让并发 append 产生 false-current 窗口。
除此之外 Memory 不得直接读取物理 Tape 表。

projection current 时只 materialize cursor 区间；head 不一致时，runtime 通过 `TapeRawEntryReader`
构建 effective Tape view 并重建 projection。projection 查询或重建失败时保留既有 Tape fallback 和
cursor commit 保护，不能因为拆层新增全历史 hot-path 查询，也不能在不完整 projection 上推进 cursor。

`TapeRawEntryReader` 只提供 `getBySession`。Memory management route 先验证 memory row 属于请求 Agent，
再用 `getEffectiveMessageSourceSpan` 读取 retraction/replacement 生效后的最小 message DTO；manifest
列表通过 `listMemoryViewManifestsByAgent` 在 storage query 中执行 Agent、Session、message 和 limit
过滤，route 不自行解析 `payload_json` 或 `meta_json`。架构守卫同时扫描 static import、dynamic
import、CommonJS require、type import 和 re-export，Memory route 不能绕过 inspection port 重新取得
raw reader、facade 或 domain helper。

## 遗忘、lineage 与增量维护

选择性删除先在同一 SQLite transaction 中为 canonical provenance 和 normalized content 写入
domain-separated SHA-256 tombstone，再删除 claim；tombstone 不保存明文。Vector 删除发生在 durable
transaction 之后。Exact replay 被压制，语义近似但来源独立的新事实不做 embedding-level tombstone
匹配。Generic lifecycle/delete API 只管理 claim，必须拒绝 persona 和 working internal row；这些
row 只能由各自的状态机演进或重建。

Agent clear 先持久化 `claims|vectors` clear job，并立即 fence 该 Agent 的 claim read/write、
lifecycle、persona、conflict、projection 和 maintenance 路径。每个同步 SQLite transaction 最多
tombstone 并删除 256 行，同时原子维护 FTS；batch 之间让出 event loop。最后一个 claim batch 删除
derivation/dirty state 并进入 vector phase，vector cleanup 完成或被 vector manager 明确延后后才
移除 job。进程中断时，已提交 batch 不回滚；下次启动从持久 phase 继续，期间 claim 始终不可见且
SQLite trigger 拒绝 INSERT/UPDATE 逃逸。vector reset 遇到非 quarantine 的失败时 clear 仍以 fail-open
结束：manager 在进程内于下一次 lease 前重试 reset，不因此 fence 该 Agent 的后续写入。该重试是进程内
状态；若重启后 sidecar 仍残留已清除 claim 的向量，它们没有 ready certificate 因而不会被 recall
使用，并在首次 warm-up coverage 校验时作为 orphan 被批量删除。

该操作保留 tombstone，防止既有 Tape replay 重新填充，并删除 factual claim、persona 和 working
projection；它不删除 standing directive，directive trust plane 在清理期间仍可读取和管理。UI 必须
明确这个边界，不能承诺“清空所有 Memory 数据”。兼容工具名 `memory_forget` 执行的是可恢复 archive，
工具结果必须说明 row 仍在本地、只是不再参与正常 recall。
存在 pending clear job 时禁止降级到不理解该 job 的旧版本：旧 runtime 无法执行 read fence 或恢复
清理，虽然持久 trigger 仍会拒绝 INSERT/UPDATE。必须先用当前版本完成恢复。
Agent retirement 才删除整个 namespace 的 claim、directive、tombstone、lineage、dirty state 和
vector projection，使重新创建的 Agent identity 从干净状态开始。

Merge、reflection、supersede 和 manual edit 在 claim mutation 的同一 transaction 中写入 durable
derivation edge。Audit 可重复记录 ID 供观测，但 retention 清理不能破坏 lineage。

自动生成使用本次输入内的 evidence ID，由 host 映射回 Tape entry 或 parent claim；模型不能直接
选择持久 ID。Extraction 新建 claim 只记录该候选引用的 Tape entry，同批等价候选合并来源，CAS
retry 保留候选上下文。UPDATE 和 existing-owner fold 保留原始来源，不把单一 source session
解释为跨会话修订历史。Reflection 只写被引用的 parent，并在写入 transaction 内重新校验 parent
的 revision、owner、scope 和 lifecycle。引用有效只证明来源存在，不证明语义蕴含或内容为真。
完整契约见 [Memory evidence attribution](./memory-evidence-attribution/spec.md)。

Committed episodic、semantic 和 reflection mutation 会 upsert `agent_memory_dirty` generation。
Maintenance 只处理有界 seed batch 和有界 same-scope vector neighbors；成功或 terminal/stale seed
才 settle，暂时失败的 generation 会轮转到未处理 work 之后，不能让固定失败前缀饿死队列。Persona
和 working projection 继续从 authoritative Agent-scope claims 重建。

## Privacy 与隔离

- 所有查询显式携带 Agent identity；不得依赖进程全局“当前 Agent”。
- User/Project/Session scope 不能跨 Agent 共享；runtime 默认只读取 Agent scope，加上当前显式
  context 匹配的窄 scope。
- private/secret-like 内容在写入、日志、metric 和 prompt contribution 前按 policy 过滤或脱敏。
- tombstone、audit refs 和 diagnostics 不得保存 forgotten plaintext。
- pending clear job 只保存 Agent ID、rowid cutoff、时间、计数和 phase，不保存 claim plaintext。
- untrusted claim、projection 和 draft directive 不能进入 executable directive channel。
- Memory tool、renderer route 和 background task 使用同一 domain normalizer。
- 删除 Agent 时先 fence 新任务、等待/取消 owned work，再删除 row、vector file 和 metadata。

## Maintenance 和可观测性

`MaintenanceService` 拥有 timer、cooldown 和并发预算；`MemoryService` 统一暂停与排空；`MergeService` 只负责有界
near-duplicate merge，沿用 runner 传入的 operation fence、业务时间和共享预算。用户 conflict
resolution 的后续调度由 facade 负责；自动 challenge pass 每次成功应用后通知 Maintenance 调度，
即使后续 pair 失败也不丢失已经产生的调度。`ConflictService` 不持有 Maintenance 的构造依赖。

Maintenance 使用有界 batch、deadline 和 ingestion fence。Database maintenance 顺序为：停止新任务、
fence Memory、drain accepted work、关闭 store/SQLite、执行操作、reopen、恢复后台任务。
`stopBackgroundMaintenance` 同步清空全部 prewarm/startup/consolidation timer、拒绝新的 arm 与 pass，
并对每个持有 in-flight pass 的 Agent 推进 execution fence、中止其 provider 请求，让 pass 及其委托的
challenge/merge/reflection/persona 子步骤在下一个 checkpoint 停止，而不是等完一个 provider deadline；
`drainBackgroundMaintenance` 在一个有界超时内等待 consolidation、embedding/prewarm 和 clear work
落定，超时即让 database maintenance 失败而不是带着未落定的任务关闭 SQLite。暂停期间 dirty working
refresh 不访问数据库、不删除 projection，恢复后重新调度。Clear 在批次或 await 边界暂停，保留 durable
job 并拒绝未完成的请求；如果 drain 超时使数据库维护取消，恢复原数据库后仍在途的 clear 可以继续完成。
`startBackgroundMaintenance` 在 stop 之后恢复 admission、dirty refresh 和 pending clear，并重新 arm
startup pass。
启动恢复按 Agent 顺序处理 pending clear job，避免多个遗留 namespace 在同一个 event-loop tick
同时执行首批同步事务。Shutdown 只等待当前有界 batch；未完成 job 保持可恢复。

Working projection 按 current state、stable preference/fact、recent event、plan/recurring 和 reflection
分节，使用稳定排序和 temporal annotation；排序用于 determinism、diff 和测试，不宣称带来
prompt-cache 收益。

metric 名称、retrieval evaluation 和 artifact upload 的未完成工作保留在
[memory-quality-gates-and-observability](./memory-quality-gates-and-observability/)。核心文档只记录长期
合同，不保存一次性 benchmark 数值。

## 关键入口

1. `src/main/memory/index.ts`
2. `src/main/memory/domain/`
3. `src/main/memory/core/`
4. `src/main/memory/services/`
5. `src/main/memory/infra/vectorStoreManager.ts`
6. `src/main/memory/infra/memoryVectorStore.ts`
7. `src/main/agent/deepchat/memory/memoryRuntimeCoordinator.ts`
8. `src/main/tape/ports/capabilities.ts`
9. `test/main/memory/`

Memory tests 必须防止旧 `src/main/presenter/memoryPresenter`、HNSW hot path、
无 Agent namespace/scope authoritative revalidation、directive 混入只读 memory container、明文
tombstone 和无 deadline provider call 回流。维护的 behavior fixture 覆盖 carry-forward、preference
/ directive adherence、temporal correctness 与 correction / forgetting 四轴。

---

### Architecture Session Management (`docs/architecture/session-management.md`)

# Session 管理

Session 是可长期保存的产品对象；window、renderer、Remote endpoint、Agent instance 和单次 Run
都比它短命。

## 当前所有权

| 能力 | Owner |
| --- | --- |
| create、draft、close | `src/main/session/lifecycle.ts` |
| send、queue、stop、interaction response | `src/main/session/turn.ts` |
| Agent/model/project/transfer/subagent assignment | `src/main/session/assignment.ts` |
| list、restore、status、projection query | `src/main/session/query.ts` |
| full delete transaction | `src/main/session/deletion.ts` |
| transcript | `src/main/session/data/transcript.ts` |
| Tape public port / composition | `src/main/session/data/index.ts` |
| Tape domain / application / SQLite adapters | `src/main/tape/` |
| generation settings / Memory cursor | `src/main/session/data/settings.ts` |
| pending input | `src/main/session/data/pendingInputs.ts` |
| renderer binding | `src/main/desktop/sessionBinding.ts` |
| backend selection | `src/main/agent/manager/agentManager.ts` |

各入口只接收自己需要的 Session port。不存在聚合全部能力的 Session facade，也不允许 window
binding 进入持久化 Session 数据。

## 创建与发送

```mermaid
sequenceDiagram
    participant E as Desktop / Remote / Scheduler
    participant S as SessionLifecycle / SessionTurn
    participant D as Session data
    participant M as AgentManager
    participant B as DeepChat or ACP backend

    E->>S: create / send canonical input
    S->>D: persist Session and user input
    S->>M: resolveSessionHandle(sessionId)
    M-->>S: typed backend handle
    S->>B: initialize / send / cancel
    B->>D: persist projection and Tape
    B-->>E: typed state events
```

route 只做 schema 和 transport adapter。所有入口在进入 `SessionTurn` 前必须得到同一种 canonical
send input；Remote、Scheduler 和 renderer 不得各自维护不同的默认值或 permission 语义。

## Queue 与 Steer

Queue 和 Steer 共用 pending-input persistence，但产品语义不同：

```text
Queue: bottom draft lane -> claim -> user message -> assistant message
Steer: user message (Unread) -> claim (Read) -> new assistant message
```

- Queue 在 claim 前不是 transcript fact，可编辑、排序、删除；容量只计算 active Queue。
- Steer 接受时由 `SessionPendingInputs` 在一个 SQLite transaction 中同时写入 user message 和
  pending batch。快速连续 Steer 各有独立 user message，但复用当前 pending batch 的合并 payload。
- Steer claim 是不可回退的读取边界：同一 transaction 更新 batch、写入所有 linked message 的
  `readAt`，并预留新的 assistant message ID。claim 后不允许走 Queue 的 release/retry 语义。
- DeepChat 在现有安全 yield boundary 结束旧 assistant；ACP 使用 `pending_input` cancel cause
  结束旧 projection。两个 backend 都把后续输出写入 claim 预留的新 assistant message。
- renderer 只把 Queue 渲染在 `PendingInputLane`。Steer 通过 typed route result 和
  `sessions.messages.changed` 进入正常 message list；event 是 cache update，SQLite transcript
  仍是 source of truth。
- receipt 只由持久化 `readAt` 派生为 `Unread` 或 `Read`。`Read` 的短时显示和淡出属于 renderer，
  不产生延迟数据库写入。
- 冷启动是单独的终止边界：上个进程尚未 claim 的 Steer 会消费 pending row，并把 linked user
  message 内部终止为 `error`。renderer 不展示恢复专用 receipt 或按钮，仍保留普通消息工具栏；已经
  claim 的 Steer 保留 sent user fact，由中断的 assistant message 承载失败。

## 恢复与查询

- `sessions.restore` 返回最近一页，`sessions.listMessagesPage` 使用 keyset pagination 拉取旧历史。
- 普通 list/history/binding query 不 hydrate Agent instance。
- DeepChat harness 构造时会先 reconcile active pending inputs。历史 Queue row 保持 durable，并进入
  process-local restart hold；Session 打开、hydrate、消息查询和 pending-input list 都不会释放 hold
  或触发执行。用户通过 Queue lane 的 `Resume queue` 显式释放当前 Session 的 hold，之后继续沿用
  Steer-first 与 Queue FIFO drain 规则。手动恢复的 Queue head 在写入 user/assistant fact 并进入
  provider Run 前消费；manual 标记绑定 Queue item ID，附件 block 后的 retry/degrade 不会丢失该语义。
  此后的 provider error 只保留在 transcript，不会把同一条目放回 Queue。pending-input list 同时返回
  后端计算的 resume availability；普通 live Queue 不显示该操作，无 hold 的 Resume 不触发 drain。
- 重启前尚未 claim 的 Steer 不再自动 drain：reconciliation 消费对应 pending row，并把 linked
  pending user message、Tape replacement 与 row 消费在同一事务内终结为 `error`，UI 不显示
  receipt。用户通过普通消息工具栏 Retry 时走普通 turn，且不会释放同 Session 的历史 Queue hold。
- claimed Queue 创建 user message 时会在同一事务内回写 pending row 的 message ID，使冷启动可以
  明确区分尚未开始的 draft 与已物化的 transcript fact。
- restart hold 仅由现有 active Queue ID 派生，不持久化、不改变 Queue 排序和容量，也不新增 schema；
  再次重启会从剩余 durable row 重建。pending-input list 是纯读。
- DeepChat harness 构造时在 pending-input 与 transcript recovery 之前分类 Execution Journal。存在
  dispatch-without-outcome、corruption 或缺失 terminal 的 Run 只输出结构化 parked 诊断，不依据该报告
  自动重放遗留 operation；分类先于 runtime graph 构建，Journal 读取失败会阻止 harness 构造。诊断最多
  输出 100 条清理过控制字符的明细，超出部分只输出分类汇总。v1 不新增持久化 Session parking 状态。
- Session status 不持久化：已载入时来自 backend snapshot，未载入为 `idle`。
- structured transcript 是当前 read model；legacy conversations/messages 仅用于一次性 import 和明确的
  export conversion。
- history search 优先使用 search document / FTS path，失败时回退受控 SQL search；坐标和 scroll
  归 renderer viewport owner，不写回 Session。

## Tape boundary

Session data composition 创建一个 `SessionTape`，对外继续暴露现有 `SessionTapePort`，并按每个 IPC
操作原有的条件和时序调用 `ensureSessionTapeReady`。Tape policy 与 persistence 全部位于
`src/main/tape/`；`src/main/session/data/` 下不再有 Tape 的 compatibility re-export 或 table path。

- transcript 只接收 `TapeMessageFactWriter`；message replace/retract 与对应 Tape fact 继续共享调用方
  SQLite transaction；
- settings/compaction 只接收 anchor reader/writer 与 lifecycle admin；summary 更新和 anchor append
  继续使用同一个 connection；
- loop runner 接收 Context Tape、provider attempt 与 strict Execution Journal writer；harness composition
  只接收 Journal recovery reader。Turn coordinator 与 ACP compatibility 只接收 reconciliation；Memory
  和 routes 分别接收自己的最小 Tape capability，不接收物理 table；
- transcript backfill 只可生成 legacy Context facts，不能创建 `execution/*`；Run、dispatch、outcome 与
  terminal facts 只能由 runtime 的 strict writer 在原生边界提交；
- Tape 的运行中修订通过 append 表达；物理 delete/reset 只由 Session lifecycle 触发。

`SessionTranscript` 和 `SessionSettingsStore` 不提供隐式 `new SessionTape(...)` fallback，必须由正常
composition 注入共享 connection 上的最小 capability。legacy import 作为 migration consumer 复用
`sessionData.tapeStore` 的 message fact writer，不再构造第二个 facade，避免隐藏的独立 writer 或事务
上下文。

`clearSessionMessages` 会创建新的 Tape incarnation：pending input 删除、transcript 删除和 Tape reset
位于同一个外层 SQLite transaction；Tape 内部的 entry、mutation projection、search/FTS projection
删除和新 bootstrap 作为 savepoint 嵌套。lifecycle、cleanup 或 bootstrap hard failure 会同时恢复上述
数据并完整保留旧 incarnation。mutation projection 沿用 fail-open：新 bootstrap 的 projection apply
失败时旧 projection row 已删除且 meta 标 stale。最终 Session delete 不创建新 incarnation，继续遵循
下面的 staged cleanup 顺序。

## Binding

`DesktopSessionBinding` 维护 `webContentsId -> sessionId`：

- activate 读取 projection 并绑定 renderer；
- deactivate、tab close 或 window destroy 只删除 Desktop binding；
- 关闭最后一个 window 不默认 cancel Turn、clear permission、evict runtime 或 delete Session；
- Remote 和 Scheduler 有自己的 binding/run identity，不能复用 Desktop 状态。

## Close、delete 和 transfer

`handle.close()` 停止并驱逐所选 backend runtime，但保留 app-session shell。Full delete 的顺序是：

```text
cleanup both backend caches without hydration
  -> remove ACP durable binding
  -> remove transcript / Tape lifecycle data / pending / settings
  -> clear permission and active Skill state
  -> delete app-session row
```

因此 missing、disabled 或 malformed agent row 不会阻止旧 Session 被删除。

Transfer 先验证 target descriptor 和 kind-specific setting，再提交 assignment，最后关闭旧 runtime。
失败不得留下半迁移 Session。删除 Agent 时使用相同的 descriptor-independent cleanup，不直接删除用户
Session 数据，除非用户执行明确的 Session 删除操作。

## Project 和目录

Session 只保存 `projectDir`。目录生命周期、默认目录、archive/remove/reorder 和文件是否存在由
`src/main/project/` 负责；Workspace 负责访问授权与文件能力。移除目录不会删除真实文件，相关 Session
按产品合同转为 no-project 或保留 ACP resume 所需 workdir。

## 防回归

- Agent 或 Session 不得依赖 Desktop、Remote、Scheduler、Routes 或 App。
- query 不得偷偷 launch ACP process 或 hydrate DeepChat instance。
- app-session row 必须在所有 owned data 清理完成后最后删除。
- `new_sessions.subagent_enabled` 只作为旧数据库兼容列，不能重新成为授权来源。
- 当前入口从 `src/main/session/routes.ts`、`sessionService.ts`、`chatService.ts` 追踪；不要恢复
  `SessionPresenter` 或 `AgentSessionPresenter`。

---

### Architecture Tape System (`docs/architecture/tape-system.md`)

# Tape 系统

## 惰性的 Skill materialization fact

Tape 物理支持 `context` entry。`skill/materialized` 当前 schema 3 是完整 effective Skill content
及其有界 execution package 的保留 canonical fact：它绑定 Session 和 Tape incarnation、按内容寻址，
并且只能通过由 `SessionTape` 组合的窄 materialization reader/writer 访问。schema 2 的 scripts-only
payload 保持兼容读取；只存在于未合并开发分支的 schema 1 被显式拒绝。generic append 不能伪造该
保留 fact name；DeepChat materializer 只获得冻结的窄 capability adapter。复用会在同步事务内验证
canonical payload 全等和 hash。

`context` 不进入 effective view、transcript、Tape search/context tool 或 search/Memory projection，
Inspector 也只暴露它的 hash/引用元数据。消息级和 Session 级上下文由 ViewManifest schema 6/hash 4 引用；schema 6 也兼容读取不含可执行权限的早期 runtime
view occurrence。带 runtime `skill_view` 可执行权限的上下文由 schema 7/hash 5 同时绑定 provider
可见的 `tool_result` 和 execution package。manifest 只保存引用与证明，不成为内容 sidecar；schemas
1–5 以及 schema 6 的既有 hash 与语义保持不变。

Tape 是 Session 同寿命的 append-only fact store，在同一个物理 entry 序列中承载三族语义隔离的事实：

- Context Tape 保存可回放消息事实、anchor、ViewManifest、provider attempt 和 Subagent lineage，
  服务 context assembly、recall、replay 与审计；
- Execution Journal 保存 Run、工具副作用和终态的原生边界事实，服务失败分类与崩溃后对账；
- Contract lineage 保存冻结的任务语义和验收裁决，服务 live delegation 的约束、交接、评价与审计。

message transcript 是 Context Tape message fact 的派生投影和 UI read model：终态消息先 append 为
fact，再在同一事务内由 `TranscriptProjectionApplier` 从该 fact 的 record 派生 transcript 各表；流式
中间态只留在 transcript 的 pending 区。transcript 不是 Execution Journal 的 authority。transcript
reconciliation 只在 Session 尚无当前 incarnation 的投影游标时把既有 transcript 一次性回填进 Context
Tape，不得制造 `execution/*` 或 `contract/*` 事实。live-delegation row 和 mailbox 是 Contract fact 的
在线 projection；Tape 保存历史事实，但不成为在线权限或编排状态的 authority。

## 所有权和分层

| 能力 | 当前 owner |
| --- | --- |
| entry/fact/ref、effective semantics、ViewManifest/replay 纯逻辑 | `src/main/tape/domain/` |
| 消费方能力和 storage ports | `src/main/tape/ports/` |
| Fact、Execution Journal、Contract、Reconciler、Recall、Lineage、View/Replay services | `src/main/tape/application/` |
| `SessionTape` 兼容 facade | `src/main/tape/application/sessionTape.ts` |
| append/read/query store | `src/main/tape/infrastructure/sqlite/tapeEntryStore.ts` |
| search projection | `src/main/tape/infrastructure/sqlite/tapeSearchProjectionStore.ts` |
| 物理 lifecycle delete | `src/main/tape/infrastructure/sqlite/tapeLifecycleAdapter.ts` |
| runtime assembly | `src/main/agent/deepchat/runtime/tapeViewAssembler.ts` |
| policy selection | `src/main/agent/deepchat/runtime/tapeViewPolicy.ts` |
| model-facing tools | `src/main/tool/agentTools/agentTapeTools.ts` |

Tape entry 只能 append。更正、压缩和 handoff 通过新 fact/anchor 表达，不原地改写旧 entry。
anchor 改变后续读取起点或重建状态，但不删除被覆盖的历史。

```mermaid
flowchart TD
    Consumers["Agent / Transcript / Memory / Settings / IPC"] --> Ports["Tape capability ports"]
    Ports --> Facade["SessionTape compatibility facade"]
    Facade --> Services["Application services"]
    Services --> Stores["Entry store / Search projection / Lifecycle adapter"]
    Stores --> SQLite["Shared Session SQLite connection"]
```

Tape 代码只从 `src/main/tape/` 或能力 port 导入。`src/main/session/data/tape*.ts` 与旧 table
modules 曾作为冻结的 deprecated compatibility re-export 存在，生产代码归零后已删除；不得再在
`src/main/session/data/` 下重建 Tape 的转发路径或 owner。

## 能力端口和组合

| 消费方 | 允许依赖的 Tape 能力 |
| --- | --- |
| DeepChat loop runner | `DeepChatLoopTapePort`（manifest、Skill request/runtime-view authority、tool fact、provider attempt 与 Journal 的窄能力组合） |
| DeepChat Skill materializer | 冻结的 `SkillContextTapePort` adapter（incarnation、materialization、有效 user source 与 Run-manifest 能力） |
| DeepChat Skill execution authority | 冻结的 `SkillExecutionAuthorityTapePort` adapter（exact execution ViewManifest、incarnation 与 materialization read-only 校验） |
| DeepChat harness composition | `ExecutionJournalRecoveryReader` |
| Deferred tool executor | `ExecutionJournalWriter` |
| Interaction coordinator | deferred approval recovery 所需的 `ExecutionJournalRecoveryReader`、exact execution ViewManifest reader 与 tool-surface fact reader 窄组合 |
| Live delegation repository | `ParentTaskContractWriter`、`TaskContractWriter`、`TaskEvaluationWriter` |
| Turn coordinator / ACP compatibility | `TapeReconciliationPort` |
| Transcript | `TapeMessageFactWriter`、`TapeProjectionHeadReader`；同时实现 reconciliation 依赖的 `TapeTranscriptProjection` |
| Memory runtime | `TapeNonContextEntryReader`、`TapeAnchorWriter` |
| Settings / compaction | `TapeAnchorReader`、`TapeAnchorWriter`、`TapeLifecycleAdmin` |
| Memory routes | `TapeInspectionReader` |
| IPC / Session data | 现有 `SessionTapePort` |

`createSessionDataFromDatabase` 组合一个 `SessionTape`，把窄能力传给 transcript 和 settings，并在既有
IPC boundary 按原时序执行 `ensureSessionTapeReady`。facade 只做 service 组合和兼容转发，不承载新的
domain policy；外部方法的签名、同步/异步行为、异常和 fallback 语义保持稳定。

`TapeNonContextEntryReader` 只暴露 Memory runtime 实际需要的 `getBySession`。Memory routes 使用的
`TapeInspectionReader` 只返回 effective message source span 与 Memory ViewManifest DTO，不返回
`DeepChatTapeEntryRow`。完整的 manifest assembly source set 命名为
`TapeViewManifestAssemblySources`，domain lookup map 命名为 `TapeViewManifestLookupMaps`；历史别名
`TapeViewManifestSourceMaps` 已随 compatibility path 一起移除。
`TapeAnchorReader` 只暴露 settings 实际使用的 latest reconstruction anchor；transcript/settings 必须
由 composition 注入 port，不允许在 consumer 内隐式构造 concrete facade。

## 存储与事务边界

- `TapeEntryStore` 只负责 append/read/query；物理删除由独立 lifecycle adapter 执行，只服务于
  Session lifecycle，不属于运行中 Tape 语义。
- Execution Journal 使用同一个 SQLite connection 上的同步 transaction。事务内完成 prerequisite、
  identity collision、payload equality 和 append 检查；同 identity 同 payload 返回既有 receipt，同
  identity 异 payload 报 corruption。它记录已越过外部副作用边界的事实，所以必须独立提交并拒绝加入
  调用方事务；strict commit 失败必须向调用方传播。
- `contract/*` namespace 由 strict Contract writer 独占，generic Tape append/query projection 不得伪造。
  Contract writer 校验 canonical payload、当前 Tape identity、因果引用和幂等冲突，并要求加入
  live-delegation 的宿主事务。每个 Contract fact 分别与它触发或证明的 runtime mutation 原子提交；不同
  lifecycle boundary 之间不共享一个长事务。
- transcript message mutation 与 replacement/retraction fact、summary compare-and-set 与 anchor append
  使用同一个 SQLite connection 和调用方 transaction，拆层不能拆开其原子边界。终态 transcript 写入的顺序
  固定为 fact append → 表投影 → 推进 `deepchat_transcript_projection_meta` 游标；游标只由 reconciliation
  的一次性回填创建，写入只能推进已建立的游标。
- `clearMessages` 在同一外层 transaction 中删除 pending input、transcript projection 并 reset Tape；
  Tape generation transaction 作为 savepoint 嵌套，任一 hard failure 会同时恢复三类数据。
- `resetSessionTape` 在同一 transaction 内删除 entry、mutation projection、search/FTS projection 并
  创建新 bootstrap；lifecycle/cleanup/bootstrap 的 hard failure 会恢复旧 incarnation。既有 mutation
  projection append fail-open 策略仍可提交新 Tape，但旧 projection row 已删除且 meta 会标 stale。
- context projection 通过单条 `getByEntryIdsIfCurrent` SQL 校验 projection version、projection meta head
  与同步调用方提供的 current Tape head，并读取请求行；不 current 时从 effective Tape 重建
  summary/ref context。
- search projection 升为 version 3，一次性拒绝可能来自 pre-atomic reset、恰好复用相同 head 的 version
  2 row；current search 按需重建，linked read-only search 在重建前沿用 effective-Tape fallback。
- search projection 可以重建；projection 不可用或 coverage 不完整时回退 effective Tape search。
- legacy chat import 的全表删除是 migration-only 例外，但消息 fact writer 复用 composition 已创建的
  `SessionTape` capability，不再另建 facade；Memory ingestion projection 为避免并发窗口，可以在一条
  只读 SQL 中同时比较 Tape head 和 projection head。除此之外消费方不得访问物理 Tape 表。
- reset 物理删除当前 Session Tape 后重新 bootstrap；本阶段没有 archive-on-reset，不能把 reset 解释成
  append-only 运行语义的一部分。

写入事务取决于事实所描述的边界，不按 namespace 机械统一：

| Fact/path | 失败策略 | 事务纪律 |
| --- | --- | --- |
| Context message/anchor | 沿既有交互 settlement policy | 与对应 transcript/projection mutation 同事务或按既有 fail-open 规则提交 |
| interactive `view/assembled` + applicable tool-surface provenance | fail-open，记录 bounded diagnostic | provider request 前由 dedicated writer 在一条事务中 append/validate |
| contract-bearing `view/assembled` + applicable tool-surface provenance | fail-closed | provider request 前由 dedicated writer 在一条事务中 durable append/validate |
| `execution/*` | fail-closed | 跨外部副作用边界独立提交，拒绝宿主事务 |
| parent `contract/task_frozen` | fail-closed | 与 delegation/turn 创建同一事务 |
| child `contract/task_frozen` inherited copy | fail-closed | 与 dispatch preparation projection 同一事务，先于 Handoff/provider dispatch |
| `contract/evaluated` | fail-closed | 与 terminal turn/delegation projection 和 mailbox event 同一事务 |

## Execution Journal

每个 loop 或 deferred tool execution 都创建新的 UUID `runId`。一次工具 operation 使用结构化身份：

```text
(runId, requestSeq, providerToolCallId)
```

`requestSeq` 复用 provider payload 的现有序号；provider tool call ID 只在该 Run/request namespace 内
解释，不能假定跨响应或跨 provider 全局唯一。v1 使用四类 immutable event：

| event | commit boundary |
| --- | --- |
| `execution/run_started` | Run 注册、provider 调用或 deferred tool 执行之前 |
| `execution/dispatch_committed` | 最后一条本地 policy/argument/abort/refusal gate 之后，真实副作用调用之前 |
| `execution/tool_outcome` | 已知 success/error outcome 之后，任何 transcript/context/UI result projection 之前 |
| `execution/run_terminal` | terminal transcript、status、hook 和 renderer projection 之前 |

Journal commit 使用 strict fail-closed contract；它不继承 Context Tape producer 的 warn-only/fail-open
策略。dispatch 只保存 canonical arguments hash 与已解析 target，不保存原始参数；terminal error 只保存
hash；tool outcome 按 detailed Journal spec 和当前 `responseHash + isError` 合同只保存 canonical response
hash 与 error bit。Journal 不复制 response/error text、MCP envelope、binary 或 temporary/offload path。
Journal events 默认从 effective Context Tape view 和 search 排除，只在显式 audit view 中可见。

T1/T2 不承诺任意外部系统的 exactly-once。它把本地可证明状态限定为：

- `not_dispatched`：Run 已开始，但没有 Journal 覆盖的工具调用越过 dispatch boundary；
- `completed`：每个 dispatch 都有 outcome；
- `indeterminate`：至少一个 dispatch 没有 outcome，外部效果无法仅凭本地事实证明；
- `corruption`：identity、causal prerequisite、payload、terminal ordering 或 fact shape 冲突。

DeepChat harness 构造时先读取原生 v1 Journal facts，随后构建 runtime graph，并在之后执行 pending-input
与 transcript recovery。
`indeterminate`、`corruption` 或缺少 terminal fact 的报告会输出结构化 `parked` 诊断，且不会依据该
报告自动重放遗留 operation；明细日志最多 100 条并清理控制字符，超出部分只记分类汇总；Journal
读取失败会阻止 harness 构造。v1 的 `parked` 是 recovery disposition，不是新的持久化 Session
状态。后续显式继续执行必须创建新 Run，不复用已结束或崩溃遗留的 Run identity。

Journal v2 在保留上述 v1 provider operation identity 的同时新增 discriminated nested identity：
`(runId, requestSeq, providerToolCallId, childOrdinal)`。`childOrdinal` 是独立 child operation，不是
attempt；由 controller 按 canonical batch plan index 在任何 child approval/T1 前连续分配并冻结完整
step/template mapping，有界、不可复用。nested canonical payload 绑定真实 target、
definition/arguments/capability hash；同 identity 异 payload 是 corruption。v1/v2 并存读取且不重写历史。
outer exec T1 是 child T1 的前置，所有已开始 child 必须在 outer T2 前提交 T2，outer T2 或
`run_terminal` 后禁止 child。包含 Programmatic outer operation 的 Run 只有在 outer T2 和所有 outer/child
T1 都有 T2 时才可提交 `run_terminal`；未配对 T1 同时禁止 outer T2 和 `run_terminal`，Run 保持
unterminated/parked，不能从 startup recovery 消失。process-live parent controller/settlement receipt 是跨
CLI authority，stdout 和 Tape 都不是在线 dispatch authority。nested Journal failure/corruption 对 Run
fatal。

### Tool Surface provenance

`view/tool_catalog` 和 `view/tool_surface` 继续只描述 provider exposure。新增有界
`view/programmatic_tool_surface`，记录 request/manifest/catalog reference、programmatic capability 与
surface hash、policy/canonicalization version、bounded target/definition projection 和 degradation。三个
fact 都是同 identity 同 payload 幂等、异 payload corruption，并排除 effective View、Memory ingestion 和
普通 search。strict V5 在 provider admission 前 durable bind 且 fail closed；V4 provenance 可沿既有
fail-open，但不得冒充 verified。durable surface fact 只证明该 View 的 exposure provenance 已提交，不证明
provider request 已发送或被上游接收。正常 dispatch 不读 Tape。

`skill_run` 是窄化的安全例外：`SkillExecutionAuthorityResolver` 只能通过冻结的
`SkillExecutionAuthorityTapePort` 读取 exact execution ViewManifest、当前 incarnation 和被 manifest
引用的 materialization，并在进程 spawn 前再次校验。该读取只验证 provider-request-bound Skill
authority，不得枚举 dispatch history、从 Tape 重建 process-live authority，或授权其他工具；generic
tool dispatch 仍不得读取 Tape。

每个 View 的 Provider Active Surface 与 Programmatic Surface immutable 且 stable-target 互斥；V5
ExecutionContract ceilings 仍只含 provider-visible tools。Programmatic capability 的完整 canonical value
只存在于 process-live View/runtime；persistence 从同值派生完整 capability hash、exact bounded
ceilings/quotas 和 bounded provenance projection。Tape 不能重建 capability，也不能授权 search、call、batch
或恢复 current authority。

## View 和 provenance

每次 provider request 使用一个明确的 effective view：

```text
Tape entries + anchors + linked child head
  -> selected policy and version
  -> TapeViewAssembler
  -> ordered provider messages
  -> ViewManifest
  -> provider request trace
```

`ViewManifest` 记录 policy、version、context builder、selection reason、included/excluded entry、
synthetic contribution、anchor、token budget provenance；contract-bearing DeepChat child 的 manifest
还记录该请求的 `ExecutionContract`。正常 chat、resume、tool loop 和 context pressure recovery 都必须
记录自己的 view；不得依赖无法复现的隐式 context builder 状态。summary、reconstruction 和 Memory
生成的 synthetic user contribution 只记录 source entry ID 与 content hash，不在 manifest 中复制原文。

新的 cache-aware View 使用 `cache_aware_context_v2` / `cache-aware-v2`。它从同一次 effective Tape
projection 选择当前 incarnation 第一条有效 user fact，按 system、pinned first user、checkpoint、retained
tail、active turn 的顺序构造请求；pin 不复制或改写 Tape，也不改变连续 reconstruction cursor。该
authoritative ref 必须携带最新 effective source entry、message/order identity 与精确 provider-message
hash；写入前还必须用 Run-local raw source-content hash 校验 effective source。只有该消息仍位于受保护
前缀时才能跨 tool loop 或 context pressure 继承。缺失 Tape source、前缀漂移、raw source-content hash
变化或 protected budget 无法容纳时失败关闭，不得伪装成 synthetic provenance 或把其他同文消息归因
给首条 user fact。初始 View 固定该 Run 的 pin identity；后续 recovery 只能验证并继承，不能从变化后
的 history 重新选择另一个 pin，初始无 pin 时也不能在同一 Run 中临时引入。

Contract-bearing DeepChat child 使用对应请求的 cache-aware builder、schema version 5 和 manifest hash
version 3。普通 interactive chat 与 ACP compatibility 继续写 schema version 4，不构造或执行
ExecutionContract；schema version 1-4 与其历史 hash 语义继续兼容读取且不得原地重写。
`cache_aware_context_v1` / `cache-aware-v1` 与 `legacy_context_v1` / `legacy-v1` builder 均保留显式兼容
路径。tool loop 和 context pressure 必须继承初始 projection 的 synthetic 与 pinned provenance，不能
退化为仅按 message role 或内容相等猜测来源。

每个 schema-v5 manifest 内嵌一个与 provider payload 同时构造的 immutable `ExecutionContract`，包含：

- `ceilings`：稳定 tool target、effect、规范化 workdir binding 和 Subagent depth 上限；
- `dynamicControlSnapshot`：View 构造时的 permission、admission 和 cancellation 观测；
- `provenance`：结构化 prompt sections、provider/model、generation config、provider-visible tool
  definitions、内部 execution policy、assembler version 和可选 TaskContract ref 的 hash/identity。

同一个 contract value 绑定 request、loop run、tool batch、dispatch guard 和 manifest writer；不得用
Session-global latest-contract cache 代替。dispatch 以 typed meet 计算有效权限：集合取交集、数值上限取
`min`、effect 取偏序中更保守的一侧；workdir 则要求当前 Session 的规范化值与 frozen View 精确一致，
变化后必须构造新 View。`workspace` 字段在 V1 只表达 workdir identity/stale-View guard，不检查 tool
arguments，也不替代各工具的路径授权或 filesystem sandbox。permission、删除、撤权和 cancellation 继续
读取当前 runtime authority；frozen ceiling 只允许收缩，扩权必须等待新 View。
暂停 action 保存 request identity 与 contract hash，进程内继续使用原 value；重启后只可从该 binding 指向
且 hash 验证通过的唯一 schema-v5 manifest 恢复。contract-bearing child 的 binding 缺失、冲突或不可恢复
时 fail closed；legacy interactive action 保留兼容行为。

每份确定的 provider payload 只写一个 `view/assembled`，以 request sequence 标识；context recovery
改变 payload 并写新 manifest，transient retry 复用原 manifest。每个真正启动的 physical attempt 另
append 一个幂等 `provider/attempt_completed` event，provenance key 由 Session、message、requestSeq 和
physicalAttempt 组成，`source.seq` 继续等于 requestSeq。

Skill 渐进披露的目标合同由 `docs/architecture/skill-progressive-disclosure/` 定义。进入 provider 的
消息级或 Session 级完整 Skill 指令必须先成为 Session Tape 中内容寻址、物理 kind 为 `context` 的
materialization fact；Agent runtime 只能通过窄 writer/reader capability 写入和恢复。ViewManifest
schema 6/hash 4 记录消息级、Session 级和早期无可执行权限 runtime view 上下文的
run/request/incarnation binding、activation scope、source entry ref 与 projected hash；schema 7/hash 5
还把 runtime `skill_view` 的 exact tool-result fact 与执行物化 ref 绑定到同一 occurrence。不能用 hash
代替内容，也不能用“最新 manifest”恢复某次 Run。同一 Run 的 retry、context recovery、tool loop 和
进程内暂停续跑复用原 fact，禁止重读可变 Skill 文件；崩溃 Run 继续沿用现有 parked 语义，用户显式
发起的新 Run 才 fresh resolve 当前内容。materialization fact 默认从 transcript、effective view、
搜索、Memory 和普通 renderer 排除，避免历史行为指令通过召回重新激活。

新 attempt event 使用 schema version 2，记录 logicalRound、physicalAttempt、request/attempt origin、
failure classification、retry decision、受限错误标识、终态、stop reason、最后一个 cumulative usage
snapshot、cache read/write token 与合法的命中比率；schema version 1 保持可读。它不保存 prompt、
header、secret、raw response 或 error stack。Tape append 失败不能反向把已启动的生成改成失败。

`DeepChatContextCoordinator.streamProviderAttempts` 在 retry 决策完成后写 attempt outcome。可重试的
error control event 在决定不重放前不得进入 message projection；首个语义输出提交后，即使后续出现
transient failure 也只能保留 partial output 并结束。message metadata 汇总该 logical round 所有
physical attempt 的最终 usage snapshot，Tape outcome 则始终保留 attempt-local usage，不能用 message
aggregate 反推单次请求。retry lifecycle observer 只写诊断日志，不写 Tape。

DeepChat message trace 通过 nullable identity 列兼容旧行和 ACP trace。同一 requestSeq 的 replay 选择
physicalAttempt 最大的 trace，再按 createdAt 和 ID 稳定排序；attempt-local trace callback 必须捕获
不可变 identity。

## Context compact 与压力恢复

Tape 自身不做物理 compact。运行时 compact 的对象是 provider-visible View：reconstruction anchor
单调推进默认读取边界，旧 entry 仍留在 append-only Tape 中供 recall、replay 和审计。semantic summary
是挂在该边界上的可选重建提示，不是边界前进或 View 缩小的前置条件。

自动和压力恢复使用以下有序降级；前一步已经得到可发送的更小 projection 时跳过后续模型调用：

```text
provider candidate
  -> usage anchor + suffix estimate（信封变化时回退全量估算）
  -> compact 较旧的已闭合超大 tool result，保留最新闭合 unit 的完整证据
  -> 仍有压力：允许 compact 最新闭合 unit
  -> 仍有压力：尝试 semantic summary
  -> canonical checkpoint 严格小于它替换的当前 checkpoint + 新隐藏 View 时才提交
  -> summary 不可用或不收缩：原子写 boundary-only reconstruction anchor
  -> 从 Tape 与当前内存 active turn 重建 View
  -> semantic recovery 仅在新 View 严格更小时生效
  -> 必要时缩小 output reserve 做一次 strict retry
  -> 每个通过 fit/change 或显式 output-reduction guard 的新 payload 创建新的 requestSeq / ViewManifest
```

boundary-only anchor 使用 allowlisted 的 `summary_unavailable` 或
`summary_rejected_larger` reason 和有界 `summaryGap` coverage；不保存 provider error、stack、时间戳或
secret。连续 gap 合并为最新边界上的一个区间；旧有效 summary 只以 `priorSummary` 作为部分上下文，
不能伪装成该边界新生成的 summary。两类 gap 都保留 Tape recall 指引并在后续成功 summary 时回填；
取消 summary 时不提交边界。

成功 summary 的 checkpoint 仅在 owning reconstruction anchor 带合法 `range` 时渲染稳定的
orderSeq coverage 和 `tape_search` / `tape_context` recall 指引；legacy、仅有 `priorSummary` 或无
range anchor 不推测来源。provenance 与 summary 一起进入 synthetic contribution hash 和收缩比较，
不能成为未计预算的隐式 prompt。

active turn 不能通过重放原 user prompt 恢复，因为 tool 可能已经产生外部副作用。运行时只能压缩
assistant tool-call 与其全部结果均已闭合的 unit，保留 call/result 配对，并保护 runtime Skill、provider
replay 和 projection identity。第一阶段还保护最新闭合 unit，避免模型因丢失直接行动依据而重复有副作用的
tool；只有较旧 unit 已不足以解除压力时才能压缩它。原始 tool fact 仍在 Tape；stub 是当前 provider View
的非持久化派生投影，不推进 reconstruction cursor。

每次成功 provider attempt 可以在当前 Run 内建立 prompt usage anchor。仅当 provider、model、generation
config、provider-visible tools 和完整消息前缀 hash 全部一致时，下一次 preflight 才以 provider-reported
prompt usage 为基线，只估新增 suffix。anchor 使用请求级 generation config，不把压力下临时缩小的有效输出
上限误判为配置变化；实际发送的 fitted projection 与 continuation View 不一致、cache-read usage 超过 prompt
usage 时不建立该 attempt 的新 anchor；任一信封/前缀无法匹配已有 anchor 时回退保守全量估算。一次成功
响应会重置当前 recovery sequence latch，使后续 tool step 可再次恢复；每个 Run 最多使用三条 recovery
sequence，避免无限循环。

## Contract lineage 与评价

每个 live-delegation turn 在 parent Tape 冻结一个 `TaskContract`，内容由 `taskSchema`、`taskConfig`、
`taskDescription` 和 `taskHarness` 四部分组成。v1 harness 只验证 required Markdown level-two Handoff
sections 是否存在非空正文；它不判断任务是否完成、内容是否正确或 parent 是否接受，不支持自动 repair、
retry 或 override。follow-up 创建新 turn 和新 TaskContract，并引用同一 parent Session 的前一次
`evaluationRef`，不是复用旧 turn 或重放旧 Run；跨 Session predecessor ref 不能通过 canonical contract
校验。

parent 在创建 turn 的事务内 append `contract/task_frozen`，同时把同一 canonical contract 和完整 ref 写入
turn projection。child 在首次 provider dispatch 前把该 value strict append 到自己的 Tape，并以
`originRef` 记录 parent Session、Tape incarnation、entry 和 contract hash。这个 inherited copy 只表示
child 收到的最小任务状态，不复制 parent transcript，也不要求 child 热路径回读 parent Tape。parent 或
child Tape reset 后，runtime 可用 row 中 hash-verified canonical value 在新 incarnation append
`projection_recovery` fact 并替换 projection ref；完成前不得跨下一个 strict boundary。

每个 contract-bearing terminal settlement 必须生成一个 `contract/evaluated`。执行状态和 Handoff 格式
状态相互独立：

```text
executionStatus = completed | failed | cancelled | interrupted
evaluationKind  = handoff_format
formatStatus    = valid | invalid | indeterminate
```

`formatStatus=valid` 只证明固定 Handoff 结构满足要求，child 内容仍是不可信 evidence。一个生成成功但格式
无效的 child 仍是 `completed`，delegation 回到 `idle`，由 parent 显式 `follow_up` 决定是否继续。
settlement 在同一 SQLite transaction 中提交 Tape fact、turn/delegation projection 与 terminal mailbox
event，三者使用同一 canonical evaluation；Tape 是历史证据，row/event 是 parent 在线消费的 projection，
不构成双重 authority。

TaskContract、ExecutionContract 与 evaluation 都有独立 schema/hash/evaluator version 和 UTF-8 上限。
unknown legacy turn 不补造评价；contract-bearing turn 若无法原子写入评价则保持 recoverable，不得静默
terminal。Inspector 等派生读取只从事实和 manifests 派生；当前 schema-v5 View 已携带 per-View execution
contract，后续若扩展 task contract、attempts、evaluations 与 lineage ref，也不得成为新的事实源或在线
authority。

## Message projection 与 Context facts

- user/assistant terminal result 先写入对应 Context Tape fact，再由同一事务内的投影派生 transcript
  表；tool fact 由 loop runner 在每轮结算后写入，终态 message fact 以幂等 provenance 补齐其
  `tool_call`/`tool_result` 事实；
- provider/tool retry 不得重复提交 terminal fact；
- Context Tape 写失败按当前 settlement policy 记录/隔离，不能把已经完成的用户回复变成无限挂起；同事务
  的 fact append 失败会连带回滚 transcript 写入；
- readiness 以投影游标比对 Tape head：游标缺失或 incarnation 不匹配时把 transcript 一次性回填进 Context
  Tape、把 Tape 中 transcript 没有的有效消息投影回表，再建立游标；否则只重放游标之后的 message fact 与
  retraction。classifier 只读取原生 `execution/*` v1 events，二者不得互相伪造；
- replay 从 manifest 和 facts 重建 provider-visible context，不从 renderer block 猜测执行语义。

## Model capability

模型只可调用：

- `tape_search`：在授权 view 内查找；
- `tape_context`：读取已找到 entry 周边上下文。

`tape_info`、`tape_anchors` 是 diagnostic；`tape_handoff` 是 runtime-only。五个名称全部 reserved，
MCP 不能 shadow，持久化 disabled-tool 配置也不能关闭 system capability。

Skills 启用时，`skill_list`、`skill_view`、`skill_manage`、`skill_run` 同样是 reserved 的 system-model
capability；MCP 不能 shadow，持久化 disabled-tool 配置不能逐项关闭。全局 Skills 设置仍是其产品级
availability gate。`skill_view`/`skill_run` 的 provider-visible 结果和执行权限仍必须遵守上文的
materialization、ViewManifest、Journal 与 source fence，不因 reserved/exposure 身份获得额外 authority。

## Subagent lineage

Subagent 使用独立 Session 和独立 Tape。完成后父 Session append 一个 link，固定 child Tape head：

- 查询时只读该 frozen head，不自动读取 child 后续 entry；
- child entries 不复制进父 Tape；
- 只有显式授权的直接 child 可以跨 Tape 读取；missing、recreated 或 incarnation 不匹配必须 fail
  closed；
- 非直接 child、未授权 Session 或递归 Subagent 不能通过 Tape tool 越权读取；
- child Tape 只承载 Memory injection 的 `memory/view_assembled` anchor，不产生 `memory/extract`
  anchor：Memory runtime 不从 Subagent 会话抽取事实，parent Tape 上的事实由 parent 会话自行抽取。

Tape 没有 `fork/*` 写入方。v1.0.5–v1.0.9 的 Subagent 收尾曾在父 Tape 写 `fork/merge` /
`fork/discard` 事件（provenance `fork:<parent>:<child>:external-merge|external-discard:event`），lineage
reader 对这些历史行只读兼容：`fork/merge` 解析为已完成的 child link，`fork/discard` 仅作审计，
Inspector 仍把 `fork/*` 归入 lineage family。曾经的进程内 delta-merge fork（`fork/start` anchor、复制
delta、merge receipt）从未接入产品路径，其 merge 语义也未覆盖后来新增的 reserved namespace，已整体
移除；若将来需要同 Tape 内的并行探索，必须为每个 reserved namespace 重新定义合并语义，而不是恢复
旧实现。

## 回放和兼容

Replay 必须保持 entry order、role、tool call/result pairing、anchor cursor、policy version、builder
version 和 synthetic contribution provenance。未知旧 fact 可以按兼容规则跳过或映射，但不能静默改变
已知 fact 的含义。测试至少覆盖正常 chat、resume、tool interaction、compaction、context pressure、
Subagent frozen head、provider attempt outcome 和旧 manifest 读取。

hash 算法、`hashVersion` 与 provenance key 前缀都是落盘合同的一部分。ViewManifest 的 hashable 是 stored 字段去掉
`assembledAt`、`viewId`，并把 `hashes` 缩成 `{ promptHash, toolDefinitionsHash }`，其余字段全部进 `manifestHash`；
reader 原样透传 stored 字段后重算，所以改 hashable 变换或 canonicalizer 会让历史 manifest 校验变 `invalid`，
shape 校验要求的字段离开写入端则必须伴随新 schemaVersion；ExecutionContract、
tool surface fact 与 Journal payload 用 exact-key 校验，删除任一字段或新增必填字段都会让历史行 malformed。两类
失效都会让 `skill_run`、paused dispatch 与 pending action 恢复 fail closed，Journal 历史则被归类为 corruption。
legacy `hashJson` 不是只读兼容路径，它仍在产出 schema-4 manifest hash、tool fact provenance key 与 Execution
Journal v1 key/`responseHash`/`errorHash`，不得替换；schema 5 起的新 identity 一律用 `hashJsonData`。Tape 中的
hash 分两类：
fact 信封与 payload 的自校验（tool surface 三个 fact 的信封 hash、materialization meta `payloadHash`），以及把
Tape 之外的对象（provider 投影、process-live capability、执行包临时目录、projection row、pause binding）绑定到
entry 的凭据；后者不是对不可变 entry 的双重保证，entryId 无法替代它们。

stored manifest validation、legacy `hashVersion` normalization 和 entry-id collection 属于
`src/main/tape/domain/replay.ts` 的纯逻辑；SQLite row parsing 仍属于 `TapeViewReplayService`，不能反向放进
domain。

关键行为测试位于 `test/main/session/data/tape*.test.ts`、`transcriptAtomicity.test.ts`、
`transcriptProjection.test.ts` 与 `messageContent.test.ts`，分层守护位于
`test/main/tape/layerBoundaries.test.ts`；runtime 和 tool 契约继续位于
`test/main/agent/deepchat/` 与 `test/main/tool/`。历史的 Tape increment SDD 已合并到本文，详细实施
顺序从 Git 历史查询。

---

### Architecture Tool System (`docs/architecture/tool-system.md`)

# Tool、MCP、Skill 与 Plugin

## 所有权

| 模块 | 位置 | 职责 |
| --- | --- | --- |
| Tool | `src/main/tool/` | catalog、source mapping、执行、权限和本地 Agent tools |
| MCP | `src/main/mcp/` | server/client 生命周期、OAuth、配置和 MCP tool 调用 |
| Skill | `src/main/skill/` | 全局 Skills、Agent binding、外部快照导入、Session 选择和 Plugin contribution |
| Plugin | `src/main/plugin/` | package 安装状态、manifest 验证和能力登记 |
| DeepChat adapter | `src/main/agent/deepchat/runtime/toolAdapters.ts` | 把 loop ports 接到 ToolService 和 interaction |
| ACP adapter | `src/main/agent/acp/runtime/` | ACP protocol tools、filesystem、terminal 和 MCP config |

Plugin 只登记能力，不接管 MCP、Skill 或 Tool 的运行状态。Skill 模块是进程级 owner：mutable
Skill package、catalog cache 和 watcher 归应用级全局 Skills；Agent 只拥有 binding 和 extension
binding。Session 只保存所选 Skill 名称，不拥有文件。

## Catalog 与 source mapping

`ToolService.getAllToolDefinitions()` 组合 MCP tools 和本地 Agent tools，处理 reserved name、disabled
配置和 model-visible capability，并为每个 Session 发布独立 source mapping。

- 已发布 mapping 是执行边界；不得从另一个 Session 的 snapshot 回退。
- 只有未持久化 draft 可以使用无 Session ID 的首次执行兼容 snapshot。
- configurable catalog 只返回 `user-configurable` Agent tools，不触碰 MCP runtime cache。
- MCP 同名工具不能覆盖 built-in reserved capability。

## Tool exposure

| Exposure | 含义 |
| --- | --- |
| `user-configurable` | 可以在设置中启停 |
| `system-model` | 由 runtime policy 决定是否给模型，不受 disabled list 控制 |
| `diagnostic` | 仅诊断 API，不生成模型 definition |
| `runtime-only` | 只有内部 runtime 可以调用 |

Tape 只向模型暴露 `tape_search` 和 `tape_context`。`tape_info`、`tape_anchors` 是 diagnostic，
`tape_handoff` 是 runtime-only。所有 Tape tool name 都是 reserved。

`subagent_orchestrator` 是 `system-model` capability：只有 regular DeepChat parent、当前 Agent policy
开启且至少一个 slot 有效时可见；调用前再次解析 policy，Subagent child 永远拒绝。

## 执行与交互

```mermaid
sequenceDiagram
    participant L as DeepChatLoopEngine
    participant A as Tool adapter
    participant T as ToolService
    participant M as MCP
    participant B as Built-in tools

    L->>A: executeToolBatch(snapshot)
    A->>T: callTool(request, signal)
    T->>T: resolve Session source mapping
    alt source = MCP
        T->>M: call tool with AbortSignal
        M-->>T: result or transport error
    else source = built-in
        T->>B: call handler
        B-->>T: result
    end
    T-->>A: normalized result
    A-->>L: output or ordered interaction
```

Tool batch 在执行前应用 permission mode、文件/命令/settings 授权和问题拦截；需要用户决定时写入
ordered interaction。最后一项决定完成后创建新的 resume Run。Side-effect tool 不因 output fitting
重跑。AbortSignal 必须一直传到 MCP client/provider adapter。

一个 source-aware main-process `ToolPermissionBroker` 统一拥有 host tool consent。MCP server
`autoApprove`、session permission cache 和 server-form permission 配置已经移除；MCP App 的
same-server tool call 进入同一个 broker，且不持久化 App 专属授权。

## Agent-scoped extensions

- Agent policy 决定可用 MCP server、Plugin capability 和 Subagent slot；Skill capability 来自共享
  全局 Skills 与当前 Agent `assigned: true` binding 的交集。
- mutable package 只存放在 `<skillsRoot>/<skillName>/`。每个 Agent 的 env、runtime policy 和 script
  override 存放在自己的 binding；`.agent-scopes` 仅作为旧版迁移证据保留，runtime 不读取。
- effective Skills 是 `Session persisted selection ∩ current Agent assigned catalog`。transfer、rebind、
  fork 和 Subagent entry 都必须重新计算交集。
- 每次 Run 使用闭合 capability snapshot；同一轮中发生 unassign 或内容更新，不得改变已解析的 prompt、
  tool allow-list、script policy 或 allowed package roots。
- Agent filesystem policy 保护整个 configured Skill root；即使是 `full_access`，也只有当前 Run 的具体
  active Skill roots 可以作为例外。
- Plugin unavailable、disabled 或 uninstall 时，相关 Skill contribution 和 bindings 必须撤销，不能
  留下可执行 mapping。
- 内部 DeepChat Agent 不再是导入源；现有 package 通过 assignment 复用。外部 Agent 导入要求显式
  target Agents，先 preview，再由 main 重新扫描来源并解析 conflict。
- 外部 import 是一次性 validated snapshot，不跟随 symlink、不建立 live link，也不传播后续来源修改；
  `skip` 保留现有 Skill，`rename` 选择首个合法全局名称，`overwrite` 先重验 enabled-Agent impact。
- Plugin-owned Skills 不作为普通文件复制；外部格式 adapter 不能绕过全局 Skills containment validation。
- child Session 重新解析自己的 capability，不继承父 Session 的 assignment、active Skills 或 permission
  state。

## 调试入口

1. `src/main/agent/deepchat/loop/ports.ts`
2. `src/main/agent/deepchat/runtime/toolAdapters.ts`
3. `src/main/tool/index.ts`
4. `src/main/tool/toolMapper.ts`
5. `src/main/tool/agentTools/`
6. `src/main/tool/permission/`
7. `src/main/mcp/toolManager.ts`
8. `src/main/mcp/mcpClient.ts`
9. `src/main/skill/`
10. `src/main/plugin/`

---

### Guides Cli (`docs/guides/cli.md`)

# DeepChat CLI V1

DeepChat 随桌面应用提供 `deepchat` 命令。命令本身是薄客户端，所有 Provider、凭据、Skill、
MCP、OCR、Artifact 和 Agent 状态仍由正在运行的 DeepChat main 进程持有。

## 生命周期

- 不提供 CLI 开关。DeepChat 启动时自动启动本机 control plane。
- server 监听成功后，DeepChat 自动、幂等地安装或修复自己拥有的 `deepchat` launcher。
- The owned launcher is a regular file that references the validated CLI and packaged Electron
  executable. It sets `ELECTRON_RUN_AS_NODE=1` for the CLI host and refreshes atomically after an
  application move or upgrade; owned legacy symlinks migrate automatically.
- The launcher does not fall back to a system Node on `PATH`; a missing packaged host fails closed
  with exit code `127`.
- launcher 冲突时保持失败关闭：不会覆盖同名的外部命令、被修改的 managed block、符号链接
  profile 或不属于 DeepChat 的文件。
- launcher 不是 daemon。DeepChat 未运行时，命令返回 `unavailable`，退出码为 `3`。
- DeepChat 退出时先停止接收请求，再取消进行中的 RPC、上传、下载和 stream。连接会在有界宽限期
  内关闭，所有已连接且正在等待 main 的 CLI 进程自行退出，退出码为 `3`；不会遗留 CLI 后台进程。
- 普通退出保留 launcher，便于下次启动后直接使用。完整数据重置只删除仍能证明由 DeepChat
  拥有且未被修改的 launcher 集成；ownership 冲突或清理失败会保留外部命令/profile 并记录安全
  诊断，但不会阻止应用数据重置。

先用诊断命令确认桌面端和协议可用：

```bash
deepchat system status --json
deepchat system version --json
deepchat system capabilities --json
deepchat system doctor --json
```

## 命令合同

所有命令都使用固定的两段式前缀：

```text
deepchat <domain> <verb> [options]
```

`--json`、`--jsonl`、`--timeout` 和领域参数必须放在 domain 与 verb 之后。下面的形式会被拒绝：

```text
deepchat --json image generate
```

这不只是命令风格。Agent shell 的会话权限按命令签名缓存，两段式前缀确保批准粒度稳定在
`deepchat <domain>`，不会因为前置 flag 变成另一组权限。

查看全部命令或单个命令参数：

```bash
deepchat help
deepchat image generate --help
```

输出模式：

- 默认 text：给人阅读，stdout 只放结果，诊断信息写入 stderr。
- `--json`：只输出一个稳定的 result 或 error envelope。
- `--jsonl`：逐行输出有版本的事件，最后一行是终态 result 或 error。
- streaming 命令在 text/JSON 模式由 CLI 有界收集；main 始终只维护一条 canonical stream。
- machine 模式不输出 ANSI progress UI。

稳定退出码：

| Code | Meaning                                           |
| ---- | ------------------------------------------------- |
| `0`  | success                                           |
| `2`  | invalid command or input                          |
| `3`  | DeepChat unavailable or protocol/surface mismatch |
| `4`  | authentication or authorization failure           |
| `5`  | renderer approval denied or timed out             |
| `6`  | domain operation failed                           |
| `7`  | timeout, signal, or cancellation                  |
| `8`  | internal or protocol failure                      |

## V1 能力清单

| #   | 能力                  | 命令域                                                                 | 关键边界                                                         |
| --- | --------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------- |
| 1   | 文本模型调用          | `model invoke`                                                         | raw provider stream，无 Session、Tool、Memory 或 Skill 副作用    |
| 2   | 图片生成              | `image generate`                                                       | 二进制结果进入 ArtifactSpool                                     |
| 3   | 音频生成与识别        | `audio speak`, `audio transcribe`                                      | speech 为正式 standalone 能力；转写支持上传或 owned artifact     |
| 4   | 视频生成              | `video generate`                                                       | 二进制结果进入 ArtifactSpool                                     |
| 5   | 离线 OCR              | `ocr status`, `ocr extract`, `ocr clear-cache`                         | 图片/PDF，文本内联返回，不进入 ArtifactSpool                     |
| 6   | 完整 Agent run        | `agent run`, `run get/watch/cancel`                                    | durable detached Session，可恢复、订阅和幂等取消                 |
| 7   | 公共设置              | `settings get/set`                                                     | 只读写 typed allowlist，不是任意配置通道                         |
| 8   | Provider 管理         | `provider list/test/add/update/set-credential/clear-credential/remove` | 公共 DTO 脱敏；凭据只从 stdin 进入 main                          |
| 9   | Model 管理            | `model list/enable/disable/config-get/config-set/config-reset`         | 运行时列表与严格公共配置分离                                     |
| 10  | Skill 管理            | `skill list/install/enable/disable/remove`                             | ZIP/HTTPS 安装有边界与供应链批准                                 |
| 11  | MCP 管理              | `mcp list/add/update/enable/disable/start/stop/remove`                 | 仅公开管理面，不暴露 raw MCP tool tunnel                         |
| 12  | Artifact 管理         | `artifact describe/get/delete`                                         | ownership、TTL、hash、配额与跨文件系统 no-overwrite              |
| 13  | 诊断和 benchmark 输出 | `system ...`, JSON/JSONL、stdin、timeout                               | 外部 harness 负责数据集、重复、打分和冷启动                      |
| 14  | Agent scoped CLI      | bundled `deepchat-cli` Skill                                           | main 签发短期、按调用和字节限额的 token，不暴露 human descriptor |

## 模型、媒体与 OCR

先枚举可用 Provider 和模型 ID，不要根据 UI 名称猜测：

```bash
deepchat provider list --enabled-only --json
deepchat model list --provider <provider-id> --json
deepchat model config-get --provider <provider-id> --model <model-id> --json
```

模型与媒体调用示例：

```bash
deepchat model invoke --provider <provider-id> --model <model-id> \
  --prompt 'Explain the result' --jsonl

deepchat image generate --provider <provider-id> --model <model-id> \
  --prompt 'A product photo' --jsonl

deepchat video generate --provider <provider-id> --model <model-id> \
  --prompt 'A five second product turntable' --jsonl

deepchat audio speak --provider <provider-id> --model <model-id> \
  --text 'Hello from DeepChat' --jsonl

deepchat audio transcribe --provider <provider-id> --model <model-id> \
  --file ./sample.wav --json
```

较长的 prompt、speech 文本和敏感值应通过 stdin 传递，避免 shell quoting 与 process-list 暴露：

```bash
deepchat model invoke --provider <provider-id> --model <model-id> --stdin --jsonl
deepchat provider set-credential --provider <provider-id> --stdin --json
```

OCR 是独立的随包离线能力，不是模型别名，也不依赖聊天里的“非视觉模型自动提取附件”设置：

```bash
deepchat ocr status --json
deepchat ocr extract --file ./scan.png --json
deepchat ocr extract --file ./document.pdf --page-count 12 --max-tokens 8000 --json
deepchat ocr clear-cache --json
```

OCR 输出记录真实的 cache/runtime 状态：

- `hit`：命中派生缓存；
- `miss-warm`：未命中缓存，提取前 helper 已 ready；
- `cold-runtime`：未命中缓存，提取前 helper 尚未 ready；
- offline：runtime asset 不可用，调用以 typed unavailable error 结束。

`ocr clear-cache` 只清理可再生的派生缓存，不重启 helper，也不保证制造 cold-runtime 样本。严格的
冷启动 benchmark 应由外部 harness 重启 DeepChat，并同时记录 app/protocol/surface 版本、
`runtimeStateBefore`、输入大小、耗时和输出 token 数。

## Artifact 与文件边界

图片、视频和 speech 的二进制结果以临时 Artifact 返回。结果包含随机 ID、MIME、大小、SHA-256、
过期时间和建议文件名，不包含 main 内部路径。

Human terminal 可以读取或删除自己可见的 Artifact：

```bash
deepchat artifact describe --id <artifact-id> --json
deepchat artifact get --id <artifact-id> --out ./result.png --json
deepchat artifact get --id <artifact-id> --out ./result.png --overwrite --json
deepchat artifact delete --id <artifact-id> --json
```

`artifact get` 默认不覆盖现有文件。main 不接受输出路径；它只流式返回受 ownership 保护的字节，
最终路径由 human CLI 在本地处理。

输入同样按 caller 分流：human CLI 打开本地文件并上传有界字节，main 不接受任意输入路径；Agent
只能传递 DeepChat-owned Artifact ID。Agent 不能使用 `--file`、`--out`、`--overwrite`，不能下载或
删除 Artifact 字节。

## Agent run

完整 Agent 工作流与 raw `model invoke` 分开：

```bash
deepchat agent run --prompt 'Inspect the project and summarize the issue' --json
deepchat run watch --run <run-id> --jsonl
deepchat run get --run <run-id> --json
deepchat run cancel --run <run-id> --json
```

`agent run` 先创建 durable detached Session，再启动首轮。CLI 断开不会删除 run；可以通过
`run get` 恢复消息和 `running | awaiting_interaction | terminal` phase，human caller 可通过 cursor
续接 `run watch`。一次 provider stream 完成或失败不会提前结束 watch；只有 root Session 进入
`idle`/`error` 才是整个 detached run 的终态。Agent caller 自身不能递归执行
`agent run`，也不能等待当前正在执行的自身 run；Agent 仅可使用非阻塞的 `run get` 与幂等的
`run cancel`。

## 设置、Skill 与 MCP

公共读取都是脱敏的：

```bash
deepchat settings get --json
deepchat settings get --keys privacyModeEnabled,ocrBackend --json
deepchat skill list --json
deepchat mcp list --json
```

变更示例：

```bash
deepchat settings set --key ocrBackend --value '"cpu"' --json
deepchat model enable --provider <provider-id> --model <model-id> --json
deepchat skill install --url <https-url> --json
deepchat mcp add --name <server-name> --stdin --json
```

设置只覆盖 canonical contract 中的公开 key；Provider/Model、Skill、MCP 也各自使用严格输入，不能
借 CLI 读取 secret、数据库字段、环境变量或任意内部 route。

## 批准与 Agent 安全模型

Human 发起敏感 mutation 时，请求会保持挂起，由 DeepChat renderer 展示批准。批准绑定到当前
method、规范化参数 hash、effect、scope、有效期和 live request；CLI 只会等待结果，无法取得或重放
ticket，也不存在 `--confirmed` 一类绕过参数。

Agent 调用额外经过以下控制：

1. shell command permission；
2. main 签发的短期 scoped token；
3. deny-by-default `CLI_SURFACE_V2` caller/scope policy；
4. effect policy 与 renderer-only approval；
5. ownership、rate、call/byte quota 与脱敏审计。

`deepchat` 不在 `SAFE_COMMANDS`。Agent 每次只能执行一个以 `deepchat <domain> <verb>` 开头的独立
命令；pipeline、重定向、command substitution、separator 和 newline 都会阻止 scoped token 签发。
bundled Skill 不读取或暴露 human descriptor。

Agent 的管理面只开放以下批准入口：preference-only `settings set`、不带 credentials/query/fragment
的 HTTPS `skill install`，以及新增
一个默认禁用、无凭据且可完整审阅的 HTTPS remote `mcp add` 配置。Agent MCP 输入不能包含 stdio
command、headers、authorization、非 HTTPS endpoint，或超过批准 UI 的审阅上限；批准页展示完整
endpoint 与公共 metadata，而审计仍只记录脱敏摘要。`mcp update` 可能立即重启正在运行的服务，因此与
MCP runtime 控制/删除、Provider/Model 配置、Skill 启停/删除、credential 和 destructive 操作一样保持
human-only。Skill/MCP 的脱敏列表可直接读取。

## Coredev 入口

| Owner                                                   | Path                                                   |
| ------------------------------------------------------- | ------------------------------------------------------ |
| thin CLI、argv、输出和本地文件 I/O                      | `src/cli`                                              |
| server、surface、policy、domain adapters、ArtifactSpool | `src/main/cli`                                         |
| 唯一 composition/start/stop owner                       | `src/main/app/composition.ts`                          |
| canonical protocol 与 route contracts                   | `src/shared/contracts`                                 |
| 通用批准状态机                                          | `src/main/approval`                                    |
| Agent shell gate                                        | `src/main/tool/permission/commandPermissionService.ts` |
| bundled Agent instructions                              | `resources/skills/deepchat-cli/SKILL.md`               |

main 只监听 UDS 或 named pipe，不开放 TCP fallback。CLI surface 引用 canonical typed contracts，但
不是内部 route registry 的通用代理。新增能力必须显式加入 surface，并同时定义 caller、scope、
effect、approval、transport、输入/输出边界、quota 和 audit 语义。

V1 明确不包含：ACP server、远程访问、raw MCP tool invocation、TUI/交互 shell、任意配置或 secret
读取、server-side OCR batch/layout/model 管理、通用费用预算系统，以及内置 benchmark runner。
普通 Agent token 与 human client 都只使用 V2。V3 仅为冻结到 CLI Programmatic adapter 的 Agent
Run 增加受 originating View capability 约束的 `tool search|describe|call|batch`；它不是 human raw MCP
tunnel，普通 Agent token 和 V2 client 均不可达。benchmark 是建立在稳定 JSON/JSONL 合同上的外部
harness。

完整架构与安全不变量见
[`docs/architecture/local-control-plane/spec.md`](../architecture/local-control-plane/spec.md)。

---

### Guides Plugin Packaging (`docs/guides/plugin-packaging.md`)

# DeepChat Plugin Packaging

This guide documents `.dcplugin` packaging for official DeepChat plugins bundled with DeepChat
release packages.

## Package Format

A `.dcplugin` file is a zip archive built from one plugin directory.

Required files:

- `plugin.json`: hydrated manifest used by the installer.
- `checksums.json`: SHA-256 checksums for packaged files.
- every file declared by manifest skills and settings contributions.
- runtime payloads required by the target platform and architecture.

The packager excludes development-only sources such as `vendor/`, `build/`, `node_modules/`,
`.build/`, `.DS_Store`, and symlinks.

Official packages keep DeepChat release asset URLs in their manifest metadata:

```text
https://github.com/ThinkInAIXYZ/deepchat/releases/download/v<version>/<asset-name>.dcplugin
```

Output naming pattern: `deepchat-plugin-<name>-<version>[-<platform>-<arch>].dcplugin`

## Generic Commands

All plugins share a common set of commands powered by `scripts/plugin.mjs`, which delegates to
`scripts/package-plugin.mjs` for the actual packaging logic.

### Validate

Dry-run: validates the manifest and file references without producing a `.dcplugin`.

```bash
pnpm run plugin:validate -- --name <plugin> --platform <platform> --arch <arch>
```

### Package

Package into a `.dcplugin` under `dist/plugins/`. Run a plugin's native build command first when the
package needs native runtime payloads.

```bash
pnpm run plugin:package -- --name <plugin> --platform <platform> --arch <arch>
```

### Bundle

Package into `build/bundled-plugins/` for embedding into the Electron app.

```bash
pnpm run plugin:bundle -- --name <plugin> --platform <platform> --arch <arch>
```

### Verify

Verify expected bundled official plugin artifacts from plugin metadata.

```bash
pnpm run plugin:verify -- --name <plugin> --platform <platform> --arch <arch> --plugin-root <plugins-dir>
```

When `--name` is omitted, the script verifies all official plugins supported by the target platform.

### Clean

Remove all bundled plugin artifacts:

```bash
pnpm run plugin:bundle:clean
```

## Plugins with Native Build Steps

Some plugins (like CUA) include pre-compiled native binaries. These require an additional build
step before packaging. The `bundle` action automatically detects and runs
`scripts/build-<name>-plugin-runtime.mjs` when it exists. Standalone `package` expects the native
runtime payload to be built already.

CUA native runtime staging commands download pinned upstream release assets and verify their
checksums. They do not run upstream installers and do not require a PATH-installed `cua-driver`.

```bash
pnpm run plugin:cua:build              # host platform and architecture
pnpm run plugin:cua:build:mac:arm64    # macOS arm64
pnpm run plugin:cua:build:mac:x64      # macOS x64
pnpm run plugin:cua:build:win:x64      # Windows x64
pnpm run plugin:cua:build:win:arm64    # Windows arm64
pnpm run plugin:cua:build:linux:x64    # Linux x64
```

## CUA Plugin Artifacts

The CUA plugin is target-gated by platform and architecture. Supported bundled targets:

- `darwin/arm64`
- `darwin/x64`
- `win32/x64`
- `win32/arm64`
- `linux/x64`

Unsupported targets:

- `linux/arm64`

The bundled package filename includes both platform and architecture:

```text
deepchat-plugin-cua-<version>-darwin-arm64.dcplugin
deepchat-plugin-cua-<version>-darwin-x64.dcplugin
deepchat-plugin-cua-<version>-win32-x64.dcplugin
deepchat-plugin-cua-<version>-win32-arm64.dcplugin
deepchat-plugin-cua-<version>-linux-x64.dcplugin
```

Runtime detection inside the package uses architecture-specific paths. Packaged macOS builds prefer
the helper staged into the main app bundle, then fall back to the plugin-local helper:

```text
app-helper:DeepChat Computer Use.app/Contents/MacOS/deepchat-cua-driver
plugin:runtime/darwin/<arch>/DeepChat Computer Use.app/Contents/MacOS/deepchat-cua-driver
plugin:runtime/win32/<arch>/cua-driver.exe
plugin:runtime/linux/<arch>/cua-driver
```

Each `.dcplugin` contains only the runtime directory for its target platform and architecture.
Direct CUA packaging for unsupported targets fails before producing an artifact.

## Feishu Plugin Artifacts

The feishu plugin targets all platforms (darwin, linux, win32). Its MCP server uses
`node serve.mjs` which calls `npx` at runtime to download the `@larksuiteoapi/lark-mcp`
package on first use.

```text
deepchat-plugin-feishu-<version>-darwin-arm64.dcplugin
deepchat-plugin-feishu-<version>-darwin-x64.dcplugin
deepchat-plugin-feishu-<version>-linux-x64.dcplugin
deepchat-plugin-feishu-<version>-linux-arm64.dcplugin
deepchat-plugin-feishu-<version>-win32-x64.dcplugin
deepchat-plugin-feishu-<version>-win32-arm64.dcplugin
```

## Nowledge Mem Plugin Artifacts

The nowledge-mem plugin bundles a Skill and two host-managed HTTP MCP declarations. It needs no
native runtime, nmem CLI or package download at activation. All six OS/architecture targets include
it. Connection URLs and encrypted credentials belong to the host settings, not the package.

```bash
pnpm run plugin:validate -- --name nowledge-mem --platform darwin --arch arm64
pnpm run plugin:bundle -- --name nowledge-mem --platform darwin --arch arm64
pnpm run plugin:verify -- --name nowledge-mem --platform darwin --arch arm64 --plugin-root build/bundled-plugins
```

See [local verification](../features/nowledge-mem-plugin/verification.md) for connection setup.

## Memcode Plugin Artifacts

Memcode is a bundled, opt-in HTTP MCP plugin with an explicit save/recall skill.
It has no native runtime or embedded credentials. In Plugins > Memcode, configure
the HTTP server URL `https://mcp.memcode.in/mcp` and complete OAuth sign-in
through DeepChat. Connection settings and tokens remain host-managed. No
conversation is saved automatically.

```bash
pnpm run plugin:validate -- --name memcode --platform darwin --arch arm64
pnpm run plugin:bundle -- --name memcode --platform darwin --arch arm64
pnpm run plugin:verify -- --name memcode --platform darwin --arch arm64 --plugin-root build/bundled-plugins
```

## Output Locations

Standalone packages:

```text
dist/plugins/
```

Bundled packages (embedded into the Electron app):

```text
build/bundled-plugins/
```

Managed macOS helpers copied into the Electron app bundle:

```text
build/managed-helpers/
```

## CI and Release

Native plugin bundling belongs to the three reusable package workflows:

- `.github/workflows/_package-windows.yml`
- `.github/workflows/_package-linux.yml`
- `.github/workflows/_package-macos.yml`

`build.yml`, `release.yml`, and `package-regression.yml` call those workflows with an architecture
matrix instead of repeating plugin logic. The target behavior is:

- **macOS**: bundles CUA, feishu and nowledge-mem plugins for arm64 and x64.
- **Linux x64**: bundles CUA, feishu and nowledge-mem plugins.
- **Linux arm64**: bundles feishu and nowledge-mem and deliberately omits unsupported CUA.
- **Windows x64**: bundles CUA, feishu and nowledge-mem plugins.
- **Windows arm64**: bundles CUA, feishu and nowledge-mem plugins.

Electron Builder embeds `.dcplugin` files from `build/bundled-plugins/` into:

```text
<app>/Contents/Resources/app.asar.unpacked/plugins/     (macOS)
<app>/resources/app.asar.unpacked/plugins/               (Windows/Linux)
```

On macOS, Electron Builder also embeds `build/managed-helpers/DeepChat Computer Use.app` into:

```text
<app>/Contents/Helpers/DeepChat Computer Use.app
```

Each reusable target job verifies the expected bundled `.dcplugin` files inside the packaged app
before creating its package manifest. A missing required plugin fails the job. Linux ARM64 never
invokes CUA packaging, and direct CUA packaging for that unsupported target remains rejected.

Build and Release use distribution mode; package regression uses verification mode. The latter
uploads diagnostics only, so unsigned macOS verification installers and their embedded plugins
never become distributable artifacts. Release accepts the same six target manifests and publishes
app artifacts only; `.dcplugin` files are not separate GitHub Release assets.

Expected embedded files across platform-specific app packages:

```text
app.asar.unpacked/plugins/deepchat-plugin-cua-<version>-darwin-x64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-cua-<version>-darwin-arm64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-cua-<version>-win32-x64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-cua-<version>-win32-arm64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-cua-<version>-linux-x64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-feishu-<version>-darwin-x64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-feishu-<version>-darwin-arm64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-feishu-<version>-win32-x64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-feishu-<version>-win32-arm64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-feishu-<version>-linux-x64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-feishu-<version>-linux-arm64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-nowledge-mem-<version>-darwin-x64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-nowledge-mem-<version>-darwin-arm64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-nowledge-mem-<version>-win32-x64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-nowledge-mem-<version>-win32-arm64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-nowledge-mem-<version>-linux-x64.dcplugin
app.asar.unpacked/plugins/deepchat-plugin-nowledge-mem-<version>-linux-arm64.dcplugin
```

## Adding a New Plugin

1. Create `plugins/<name>/plugin.json` with required fields (`id`, `name`, `version`, `publisher`,
   `source`, `engines.platforms`, skills, settings contributions).
2. If the plugin needs a native build step, create `scripts/build-<name>-plugin-runtime.mjs`.
3. Test locally: `pnpm run plugin:validate -- --name <name> --platform <platform> --arch <arch>`
4. Add bundling commands once to the relevant OS reusable package workflow.
5. Add verification steps to that reusable workflow and update target/workflow contract tests.
6. If the plugin changes packaged resources, keep its paths covered by the package-impact
   classifier so PR package regression cannot be skipped.

---

### Architecture Agent Memory Evolution Spec (`docs/architecture/agent-memory-evolution/spec.md`)

# Agent Memory Evolution

## Contract

Agent Memory stores time-aware, correctable atomic claims with durable provenance, exact deletion
suppression, explicit trusted directives, and bounded runtime projection. SQLite owns claims and
derivation edges; per-Agent vector stores and working memory remain rebuildable projections.

Temporal validity and temporal-parse confidence are separate from factual confidence. Repeated
confirmation cannot make an uncertain date interpretation authoritative. Audit retention cannot
replace durable lineage, and forgetting cannot release the provenance key for background replay.
Trusted directives require explicit user action and remain separate from untrusted recalled data.

The architecture has no numeric Agent Memory product version. `v1` and `v2` refer only to DuckDB
vector-store formats.

## Goal

Make Agent Memory behave as a time-aware, correctable, auditable set of atomic claims while
preserving the existing ownership, failure-isolation, and bounded-runtime contracts.

The runtime model is:

```text
authoritative atomic claims
  + durable derivation edges
  + exact deletion tombstones
  + explicit trusted directives
  -> rebuildable working projection
  + query-dependent retrieval
  -> bounded typed user-role contributions
```

## Terminology

### Claim

An existing non-internal `agent_memory` row is the authoritative atomic claim. This work evolves the
row contract; it does not introduce a second fact table or a summary that can diverge from the row.

### Projection

A projection is derived, replaceable state such as the working-memory row, FTS mirror, vector
sidecar, or UI summary. A projection is never an independent fact source.

### Directive

A directive is an instruction the runtime may execute. It is stored outside `agent_memory` and is
active only after explicit user creation or approval. Model-derived text may create a draft, never
an active directive.

## Required invariants

1. `agent_memory` remains the sole authoritative store for remembered facts.
2. Existing rows migrate to an atemporal, agent-scoped interpretation without changing their recall
   eligibility. The historically non-enforced `user_scope` value remains metadata until a caller
   explicitly writes the new scope contract.
3. Temporal intervals use half-open semantics: `[valid_from, valid_until)`.
4. Factual confidence and temporal confidence are independent values with independent update rules.
5. Temporal precision is explicit; an inferred month or year must not masquerade as an exact
   timestamp.
6. Expired state claims may be excluded only when the temporal parse is sufficiently trustworthy.
   Uncertain parses fail open with a ranking penalty and an annotation.
7. A past plan is represented as “previously planned”; the system must not infer that it happened.
8. Directive content never enters the read-only memory container.
9. Active directives come only from explicit user input or an explicit approval action.
10. Forget tombstones store only hashes and metadata, never the forgotten plaintext.
11. The first tombstone contract is exact: it blocks replay of the same provenance/content identity.
    Semantic suppression of paraphrases is out of scope until behavioral evaluation can bound false
    positives.
12. Claim-to-claim derivation remains available after operational audit retention.
13. All query and mutation paths retain the current Agent ownership checks and execution fences.
14. Runtime time decisions use an injected domain clock. Infrastructure timeout measurement may
    continue to use a monotonic or wall clock local to that infrastructure.
15. Scope broadening is explicit. Missing scope context never grants access to a narrower scope.
16. Memory, projection, and directive contributions remain user-role data; none may rewrite or
    append to the base system prompt.
17. Content tombstone identity includes applicability scope. The original agent-scope hash remains
    valid for compatibility, while user/project/session tombstones cannot suppress identical
    content outside their exact scope.
18. Management and search response DTOs enforce the same temporal, scope, and directive-kind
    invariants as persistence; malformed durable state is never normalized silently at the IPC
    boundary.
19. A model-initiated `memory_remember` call is an untrusted runtime write, not explicit user
    re-authorization, and can never release a forget tombstone.
20. Generic claim lifecycle and delete APIs reject internal `persona` and `working` rows; those
    rows remain owned by their dedicated state machines.
21. Agent-wide claim clear becomes effective before its first deletion batch: claim reads and
    writes are fenced while a durable clear job exists, and a restart resumes that job instead of
    exposing a partially cleared namespace.
22. Each clear transaction processes a bounded claim batch. Tombstone insertion, authoritative row
    deletion, and FTS maintenance for that batch are atomic; directive state remains outside this
    operation.
23. Persisted support for a scope does not imply end-to-end runtime identity wiring. The capability
    matrix below is part of the public architecture contract.

## Temporal claim contract

Each authoritative claim gains:

| Field                 | Contract                                                                |
| --------------------- | ----------------------------------------------------------------------- |
| `temporal_kind`       | `atemporal`, `state`, `event`, `plan`, or `recurring`                   |
| `valid_from`          | Inclusive epoch milliseconds, nullable                                  |
| `valid_until`         | Exclusive epoch milliseconds, nullable                                  |
| `temporal_confidence` | Independent parse confidence in `[0, 1]`, nullable for atemporal claims |
| `temporal_precision`  | `exact`, `day`, `week`, `month`, `quarter`, `year`, or `unknown`        |
| `temporal_timezone`   | IANA timezone used to interpret local temporal text, nullable           |

Validation rules:

- `atemporal` has no validity bounds, confidence, precision, or timezone.
- Non-atemporal claims require `temporal_confidence`, `temporal_precision`, and
  `temporal_timezone`.
- When both bounds exist, `valid_from < valid_until`.
- Extraction outputs are normalized and rejected per candidate when malformed; a malformed temporal
  fragment does not fail the entire extraction batch.
- Existing rows backfill to `atemporal`.

Runtime interpretation:

- `state`: a high-confidence expired or not-yet-effective state is ineligible for current-fact
  recall; a low-confidence one remains eligible with a penalty and qualification.
- `event`: remains historical evidence and is annotated with its interval.
- `plan`: remains a plan; after its interval it is annotated as previously planned.
- `recurring`: remains eligible when its recurrence is not fully materialized; the stored interval
  describes the known recurrence window, not proof of an occurrence.
- `atemporal`: preserves existing behavior.

The initial hard-filter threshold is a named policy constant and is covered by behavioral tests. It
is not user-configurable in this change.

Retrieval refills FTS and vector candidates adaptively when authoritative, scope, directive, or
temporal filtering leaves fewer than top-K distinct claims. Refills reuse the query embedding,
grow geometrically, stop at 800 candidates per source, and emit `candidateBudgetExhausted` when a
saturated source reaches that hard bound without filling top-K.

## Directive contract

Directives use a dedicated durable table and typed runtime contribution.

Minimum fields:

- owner Agent;
- stable ID;
- kind: `instruction` or `suppress_topic`;
- status: `draft`, `active`, or `rejected`;
- source: `explicit_user`, `manual`, or `derived_suggestion`;
- content;
- optional normalized topic for `suppress_topic`;
- creation/update timestamps.

Behavior:

1. Explicit manual creation may create an active directive.
2. Extraction may propose a `derived_suggestion` draft.
3. Only an explicit approve action changes a draft to active.
4. Active `suppress_topic` directives gate recalled claim content before access accounting and
   assembly.
5. Active directives are rendered in a dedicated directive contribution with a separate container
   and policy version.
6. Natural-language directives are prompt-level guidance, not a hard output guarantee. Strict
   “never mention” guarantees would require output verification/regeneration and are a non-goal.
7. Because CJK suppression uses normalized substring matching, a topic containing CJK characters
   requires at least two visible base characters. New writes are rejected and malformed persisted
   topics are ignored so a single broad character cannot suppress unrelated recall.
8. The current contribution policy orders active directives deterministically by `updated_at DESC,
id ASC` before applying its hard budget. This is recency ordering, not semantic priority, and
   active persistence alone does not guarantee that an instruction fits in every prompt.
9. `priority` or `pinned` semantics require a separate product contract, persistence migration, and
   user-visible controls. They must not be inferred from directive text or source.

## Durable derivation contract

Claim derivation uses a dedicated relation:

```text
(agent_id, parent_memory_id, child_memory_id, derivation_kind, created_at)
```

`derivation_kind` is a closed set for the implemented writers:

- `merge`;
- `reflection`;
- `supersede`;
- `manual_edit`.

Rows are inserted transactionally with the resulting claim mutation. Audit may duplicate the IDs for
operational observability, but audit is not the source of truth for lineage.

## Forget contract

Explicit single-row deletion:

1. resolves the owned row;
2. rejects internal `persona` and `working` rows;
3. hashes its canonical provenance key and scope-qualified normalized content, retaining the
   original agent-scope content hash so pre-scope tombstones remain effective;
4. inserts tombstones and deletes the row in one SQLite transaction;
5. removes the vector after the durable transaction;
6. blocks future exact provenance/content recreation.

Agent-wide clear is an explicit, resumable forget operation:

1. a durable job records the Agent, a row cutoff, cumulative removed count, creation time, and
   `claims|vectors` phase;
2. the runtime immediately fences all claim reads, writes, lifecycle operations, persona
   transitions, conflict operations, projection rebuilds, and maintenance for that Agent;
3. each synchronous SQLite transaction tombstones and deletes at most 256 rows while maintaining
   the FTS mirror atomically;
4. derivation and dirty-work state are removed after the final claim batch;
5. vector cleanup must either complete or be explicitly deferred by the vector manager before the
   durable job completes; a restart resumes any unfinished phase before claim access is restored.

The job preserves tombstones so replay of existing Tape cannot repopulate the cleared data. Standing
directives remain readable and independently manageable throughout because directive clearing is a
separate trust-plane operation. Every clear control must therefore name factual memories/persona as
its target and state that standing directives are retained.

`memory_forget` is a compatibility name for soft archival. Its tool definition and result must both
say that the row remains stored locally and is excluded from normal recall; it must never claim
permanent deletion or complete forgetting.

Agent deletion is a namespace-retirement operation. It removes claims, tombstones, directives, and
derived state, so a newly created Agent identity starts clean.

## Scope contract

Memory ownership and applicability remain separate:

- `agent_id` is always the storage/security owner and vector-sidecar namespace;
- `scope_type` is `agent`, `user`, `project`, or `session`;
- `scope_id` is null only for `agent`; narrower scopes require a non-empty ID.

Existing `user_scope` remains a compatibility shadow during this evolution:

- all existing rows backfill to `scope_type=agent`, `scope_id=NULL`, because `user_scope` has never
  participated in recall and narrowing it during migration would be a behavior regression;
- new callers use the typed scope object; new writes keep the shadow synchronized for user-scoped
  rows;
- other scope types write `user_scope=null`.

This change does not introduce cross-Agent sharing. A user- or project-scoped claim remains owned by
one Agent and cannot cross its ownership boundary.

Current runtime capability:

| Scope     | Persistence / explicit API                | Normal DeepChat recall                                     | Automatic extraction          |
| --------- | ----------------------------------------- | ---------------------------------------------------------- | ----------------------------- |
| `agent`   | Supported and default                     | Fully wired                                                | Writes Agent scope            |
| `session` | Supported                                 | Wired when the current Session ID is supplied              | Does not assign Session scope |
| `user`    | Supported as an internal typed capability | No user identity is supplied by the normal chat runtime    | Does not assign User scope    |
| `project` | Supported as an internal typed capability | No project identity is supplied by the normal chat runtime | Does not assign Project scope |

Until DeepChat has authoritative user/project identity sources and lifecycle rules, user/project
scope is internal/experimental rather than an end-to-end product capability. Callers must not infer
that DTO and route support alone enables it.

## Behavioral acceptance criteria

### AC-1: Carry-forward

- Stable atemporal claims continue to be retrieved and injected across sessions.
- The new policies do not regress the maintained retrieval fixture beyond its declared thresholds.

### AC-2: Preference and directive adherence

- Stable user preferences remain recallable.
- Active directives are present in the directive contribution and absent from the read-only memory
  contribution.
- Draft and rejected directives never affect retrieval or injection.
- Suppressed-topic claims are neither injected nor access-counted.

### AC-3: Temporal correctness

- A high-confidence expired state is not presented as current.
- An uncertain expired state fails open but is qualified.
- A future plan stays a plan.
- An expired plan is rendered as previously planned and never as a completed event.
- Atemporal rows retain current ranking behavior.

### AC-4: Correction and forgetting

- Contradicting claims still follow the existing decision/conflict lifecycle.
- Exact source replay cannot recreate a selectively deleted claim.
- A later, independently sourced statement is not suppressed merely because it is semantically
  similar to forgotten content.
- Identical content in another user, project, or session scope is not suppressed by a tombstone
  from the forgotten claim's scope.

### AC-5: Consolidation and lineage

- A committed claim mutation marks bounded dirty work.
- Consolidation consumes dirty seeds and bounded neighbors instead of repeatedly scanning the full
  corpus.
- Reflection claims participate in dirty consolidation; transient failures rotate retryable
  generations behind untouched work, while stale and terminal generations are discarded safely.
- Merge/reflection outputs retain durable parent edges after audit pruning.
- Retrying maintenance is idempotent.

### AC-6: Projection and budget

- The working projection is deterministic for identical authoritative rows and clock.
- Projection ordering and temporal annotations are stable and diffable.
- One allocator reserves query-recall capacity, applies explicit floors/ceilings, and permits bounded
  borrowing of unused capacity.
- Assembled contributions never exceed the configured total memory budget.

### AC-7: Scope isolation

- Agent-scoped rows preserve current behavior.
- User/project/session rows are returned only when the matching scope context is supplied.
- A scope ID never broadens access outside the owner Agent.

### AC-8: Compatibility, reliability, and security

- Existing databases migrate without re-extraction or re-embedding solely because of new metadata.
- Existing public DTO fields remain compatible; new fields are additive.
- Memory remains fail-open for the conversation and fail-closed for stale writes.
- No forgotten plaintext appears in tombstones, logs, audit refs, or diagnostics.
- Hot-path database and vector work remains bounded.

### AC-9: Persistence and Runtime Boundaries

- Every Memory-owned test is classified into exactly one maintained gate; native trust-boundary
  tests execute in the native gate.
- Upgraded databases enforce temporal and scope invariants at write time. Import skips malformed
  rows independently, and startup neutralizes invalid temporal metadata instead of boot-looping.
  External claims repaired this way are archived rather than promoted to permanent atemporal facts;
  internal persona/working rows are normalized to their required atemporal form.
- A rewrite merge preserves the only available temporal interpretation; conflicting interpretations
  never erase time semantics without an observable audit decision.
- Malformed model-produced temporal metadata rejects only that extracted candidate.
- An explicit renderer user-add action may recreate an exactly forgotten claim atomically;
  model-initiated tools, background extraction, and replay remain suppressed.
- Scoped FTS importance candidates retain indexed ordering without a whole-Agent temporary sort.
- Directive approval text is display-safe, capacity rejection is distinguishable, and trusted
  directives are the last optional contribution shed before a hard context overflow.
- Management search remains evidence-complete even when a topic is suppressed from runtime recall.
- Persistent claim lineage contains no self-edges; in-place revisions remain observable through
  audit rather than pretending to derive a claim from itself.
- Working projection and reflection tests prove that narrow-scope claims cannot be promoted into
  Agent-wide projections.

## Compatibility and Scope Limits

- `memory_remember` preserves the structured `forgotten` domain result and maps it to
  `requires_user_reauthorization`. Only the explicit renderer add action can atomically release
  matching tombstones. Generic deletion rejects internal persona and working rows.
- Single-character CJK suppression topics are rejected at write time and ignored if already
  persisted; the directive editor explains the constraint.
- Recall oversampling grows adaptively after post-query filtering, reuses the existing embedding,
  and reports bounded exhaustion.
- Agent clear uses a durable two-phase job, immediate claim-access fencing, 256-row transactions,
  event-loop yields, and startup recovery. Completion requires vector cleanup. Directives remain
  on their independent trust plane.
- Directive ordering is recency-based and has no semantic priority or pinning contract. User and
  project scopes are persisted internal capabilities without normal-chat end-to-end identity wiring.
- Tape inspection ignores unknown allocation fields while retaining the four known lanes.
- Vector-sidecar cleanup, abort/resume access deduplication, directive lane-share tuning, downgrade
  release notes, and broader native-language translation review remain outside this contract.

## Non-goals

- Reproducing OpenAI's Dreaming implementation or claiming equivalent internals.
- Assigning an Agent Memory V2/V3 product version.
- Promoting file, tool, or knowledge-base content into long-term memory without the current policy
  and provenance path.
- Cross-Agent memory sharing.
- End-to-end User/Project scope identity wiring.
- Directive priority/pinning before its product semantics and controls are specified.
- Semantic tombstones or embedding-based topic suppression.
- A strict output firewall for natural-language directives.
- Replacing query retrieval with a single summary.
- Rewriting every infrastructure `Date.now()` call.
- Prompt-cache savings from projection ordering; ordering is for determinism and testability.

## Open questions

No implementation-blocking questions remain. Policy thresholds and initial budget shares are named
constants guarded by tests so they can be tuned without changing persisted contracts.

---

### Architecture Agent Plan Retirement Plan (`docs/architecture/agent-plan-retirement/plan.md`)

# Agent plan retirement follow-up

Implement the ownership and compatibility boundaries in [spec.md](./spec.md). Start from the merge
of PR #2384 on `dev`; keep this follow-up on a separate branch so it can be reviewed and reverted
independently. Do not edit user data or revive runtime capability.

- [x] Consolidate persisted metadata into a legacy-only shared type, move historical parsing and
      presentation to the renderer, and remove unused schemas and terminal lifecycle helpers.
- [x] Remove obsolete mocks, comments, UI identifiers and unreachable translations. Update directly
      affected specifications; retain historical implementation records with an explicit notice.
- [x] Review the entire diff for compatibility and scope. Then select existing checks and add only
      durable catalog, ACP negotiation/ignore, and historical-display regression assertions.
- [x] Run formatting, i18n, lint, typecheck, targeted main and renderer suites, and confirm unchanged
      historical rendering through the component tests. Inspect all tool-generated changes and
      remove temporary probes.
- [x] Commit the verified follow-up and push the separate branch under the requested commit/push
      workflow. Report delivery state; creating a new PR remains a separate action.

## Validation

- `pnpm run format`, `pnpm run format:check`, `pnpm run i18n`, `pnpm run lint`,
  `pnpm run typecheck` and `git diff --check` passed.
- Main: 20 suites / 467 tests passed across ACP runtime, DeepChat dispatch/process,
  Zod migration contracts and ToolService. New assertions use the real built-in catalog and
  generated prompts for Agent, Code and Minimal modes; a same-name MCP tool remains available.
- Renderer: 22 suites / 335 tests passed across MessageBlockToolCall, ChatPage, chat-page features
  and ACP components. DOM and accessibility assertions cover historical aliases, independent
  snapshots, malformed entries, and MCP-name isolation. No visual layout or style change was made;
  no new Electron E2E or screenshot run was performed for this follow-up.
- The i18n type generator exposed unrelated baseline drift. Only the removed plan status key was
  retained in the generated declaration; locale validation passed for all 23 locales.
- Existing data stays untouched. No external ACP-agent smoke test or full application suite was run.

---

### Architecture Agent Plan Retirement Spec (`docs/architecture/agent-plan-retirement/spec.md`)

# Retiring the agent plan capability

## Context and goal

PR #2384 removed the built-in `update_plan` tool and ACP plan capability. The remaining code must
describe a retired data format, not an available agent feature. This follow-up establishes that
boundary without changing the appearance or interpretation of existing conversations.

## Ownership and data flow

- Main-process tool catalogs, prompts, stream events and routes have no built-in plan capability.
  ACP initialization does not advertise `plan`; `plan`, `plan_update` and `plan_removed` notifications
  are ignored. Unrelated agent configuration modes and third-party MCP tools remain supported.
- `shared/types/legacy-agent-plan.ts` owns only the persisted plan metadata shape. All message-extra
  contracts reuse that shape instead of independently declaring its fields. These are read
  compatibility types, not schemas for new writes or tool arguments.
- `renderer/src/lib/legacyAgentPlan.ts` owns interpretation and presentation of historical
  `update_plan` tool arguments. It has no session state, IPC, runtime lifecycle or Vue composable.
  The message tool card remains its rendering owner.
- Saved ACP run plan blocks keep their existing renderer. The main transcript continues filtering
  old standalone plan blocks, as it did before retirement. This work does not add new history UI.

## Compatibility and invariants

1. Successful historical built-in calls retain their own checklist, explanation and status labels.
   Legacy `content` and `done` aliases remain accepted. Malformed calls retain diagnostic details.
2. MCP tools called `update_plan` are ordinary MCP tools, not built-in history cards or prohibited
   names. Removing a built-in capability does not reserve a global name.
3. Existing persisted field names and status values remain readable. No database migration,
   destructive field stripping or disabled-tool-list rewrite is performed. Old ACP extension
   fields may remain in stored snapshots but cannot enable a capability or produce new plan state.
4. No runtime plan schemas, terminal-state normalizers, dead terminal presentation branches or
   obsolete mocks remain. Only translations reachable from historical display remain.
5. Maintained tool-mode, ACP, permission and ChatPage specs describe the retired capability
   consistently. Historical implementation records are marked as historical, not rewritten as
   current requirements.

## Non-goals

No new compatibility service, registry, configuration switch, dependency or background migration.
No changes to user questions, permissions, queues, subagent execution or plan modes supplied by an
external agent. No repository-wide documentation cleanup or message-type consolidation.

## Acceptance and rollback

Actual built-in catalogs and generated prompts do not expose the retired tool, while adjacent
question functionality and third-party tool handling continue working. ACP clients omit plan
capabilities and all three plan notifications produce no events or blocks. Historical rendering
and malformed-input fallback remain unchanged, including accessibility labels. Repository checks
pass. The change can be reverted without touching persisted data.

## Open questions

None. Compatibility is intentionally read-only and has no automatic expiry until support for the
corresponding saved conversation format is explicitly retired.

---

### Architecture Alert Dialog Confirmation Contract Spec (`docs/architecture/alert-dialog-confirmation-contract/spec.md`)

# Alert Dialog Confirmation Contract

## Status

Implemented.

## Confirmation Ownership

A confirmation's business handler owns its pending, result, failure, and close transitions. Primitive
close behavior must not dismiss the dialog before the handler captures its target or before an
asynchronous failure can be shown. Explicit close actions and asynchronous confirmation actions
therefore use distinct contracts.

Memory commands expose typed outcomes rather than treating every resolved Promise as success.
Unavailable, missing, invalid, stale, or rejected state must produce the appropriate feedback and,
where required, reconcile stale renderer projections. Internal command-result objects are not
forwarded unchanged into model-visible tool content.

Regression tests exercise real dialog primitives at the integration seam so a plain-button mock
cannot hide close-before-handler ordering. Handler eligibility, pending state, failure visibility,
owned feedback, and disposal are part of the same confirmation contract.

## Goal

Establish one explicit confirmation contract across shared UI and Memory domain boundaries:

1. synchronous alert-dialog business handlers run before the primitive closes;
2. asynchronous confirmations that own pending or failure UI do not inherit automatic close;
3. confirmation targets survive dismissal and pending transitions through typed state;
4. rejected Memory commands retain a typed reason instead of collapsing to `false`;
5. real primitive tests protect the event-order contract and user-visible regressions.

## Required Invariants

### Synchronous close actions

- `AlertDialogAction` and `AlertDialogCancel` expose the same component-level `click` event
  contract.
- The consumer handler runs exactly once before Reka emits `update:open=false`.
- native attributes and explicit capture listeners continue to reach the rendered element.
- disabled buttons do not invoke the consumer or close the dialog.
- keyboard activation follows the same ordering as pointer activation.
- the wrapper does not wait for returned promises.

`@click.prevent` and `@click.stop` are not lifecycle APIs. Neither modifier may be used on
`AlertDialogAction` or `AlertDialogCancel` call sites. A source guard must reject both so future
code cannot accidentally depend on modifier-specific propagation behavior.

Regular close-action click handlers must be synchronously declared. A source guard must reject
locally declared `async` handlers, inline async expressions, and dynamic listener forms that it
cannot prove synchronous. A workflow that intentionally closes after synchronously retaining its
target may start background work from a synchronous handler.

### Asynchronous confirmations

- a dedicated alert-dialog action renders the same visual button contract without delegating to
  `DialogClose`;
- the owning component snapshots the operation target before starting work;
- pending state disables dismissal and repeated submission;
- success explicitly closes the dialog;
- failure keeps the dialog open with local retry context;
- async callers do not depend on a pending flag being set before the first `await` to suppress
  automatic close.
- the action's disabled predicate covers every condition that can make its handler return before
  starting work.
- controlled content remains mounted while its operation is pending, including when a related
  refresh switches the owning panel into a loading state.

OCR cache cleanup, browser sandbox cleanup, data reset, provider rate-limit disable, and inline
Memory deletion must use the explicit async contract because they already render pending or
failure state associated with their confirmation surface.

Memory directive deletion, Memory clear-all, persona rollback, and built-in knowledge
configuration removal must follow the same contract. Their confirmation target and failure
feedback remain owned by the dialog until success or explicit dismissal.

### Feedback ownership

- page or panel feedback remains visible in its owning surface while a confirmation is open;
- a confirmation renders only feedback produced by its own operation;
- dismissing a confirmation clears only confirmation-owned feedback;
- an unrelated refresh or background mutation cannot move feedback into a destructive dialog.

### Confirmation target ownership

Visibility must be derived from a typed operation state when closing the dialog can otherwise
destroy the command payload. Memory deletion and Skill overwrite must not maintain an independently
mutable `open` boolean and nullable target or callback whose lifetimes can diverge.

Dismissal may clear a confirming request. It must not clear an in-flight request.

### Memory command results

Memory commands that can reject without throwing return a discriminated result from the service
boundary:

```text
{ action: 'applied' }
{ action: 'rejected', reason: <closed domain reason> }
```

- the service preserves the reason where the decision is made;
- shared routes validate the closed result;
- renderer clients return the typed result;
- renderer callers handle both variants exhaustively;
- a rejected command displays reason-specific local feedback and does not wait for
  `memory.updated`;
- `not-found`, `invalid-state`, and `stale` rejections reconcile the local projection explicitly
  because rejected commands emit no mutation event.
- reconciliation feedback is promoted to the persistent projection owner before a stale child
  surface can be removed.

The existing `MemoryDirectiveCommandResultSchema` is the local precedent. New command reasons may
share vocabulary, but directive-specific payloads remain separate. Directive capacity remains a
directive-only reason; shared `not-found` and `unavailable` meanings reuse the Memory command
vocabulary. Directive deletion returns a structured command result rather than the last bare
boolean route.

### Refresh ownership

`memory.updated` remains the single cross-panel refresh path. The unused
`MemoryInlinePanel.changed` event is removed rather than wired to a second refresh source.
Local recovery after a rejected command may request reconciliation from the immediate projection
owner; that event reports no mutation and does not replace `memory.updated`.

### Model-visible tool output

Internal Memory command results are not forwarded wholesale to model-visible tool output.
`memory_forget` preserves its public `{ ok }` result and uses sanitized summaries; internal action
and rejection enums remain diagnostic implementation details.

## Acceptance Criteria

1. Memory list permanent delete invokes `memoryClient.remove` exactly once with the selected ID.
2. Skill overwrite invokes the retained install operation exactly once.
3. Action and Cancel consumer clicks run before `update:open=false` with real Reka primitives.
4. OCR, sandbox, reset, provider-rate-limit, and inline Memory confirmations remain open while
   pending and after failure, then close on success.
5. No alert-dialog Action or Cancel uses `.capture`, `.prevent`, or `.stop` as a local workaround.
6. A lint-time source guard rejects future `.prevent` and `.stop` call sites.
7. Rejected Memory persona and conflict commands produce local failure feedback.
8. `MemoryInlinePanel` declares and emits no `changed` event.
9. Every async confirmation keeps its own pending and failure state visible and cannot silently
   return from an enabled action.
10. Regular close actions cannot bind locally declared async or uninspectable click handlers.
11. Memory command rejection copy identifies the actionable reason and stale projections reload.
12. Delete-dialog dismissal cannot clear unrelated page or panel feedback.
13. `memory.deleteDirective` returns a structured command result and `memory_forget` exposes only
    its stable public result.
14. Pending confirmation content survives owner refresh and reconciliation feedback survives child
    removal.
15. Focused renderer and Memory suites, formatting, i18n, lint, and type checking pass.

## Compatibility

- Existing Action and Cancel props, slots, styling, and native attributes remain supported.
- The `click` listener becomes an explicitly declared component event instead of native
  fallthrough; event payload remains the original `MouseEvent`.
- Action/Cancel callback ordering intentionally changes from close-before-click to
  click-before-close.
- Memory route output changes are internal typed IPC contracts and require synchronized main,
  preload/client, renderer, and test updates. No persisted data or database migration changes.

## Non-Goals

- Replacing Reka UI or the vendored shadcn component set.
- Building a generic promise-owning dialog framework.
- Reworking unrelated Dialog, Sheet, Drawer, or notification behavior.
- Consolidating all duplicated Memory panel lifecycle code.
- Caching forwarded wrapper attributes: Vue `useAttrs()` is live but not reactive, so merging
  during render is required for updated fallthrough attributes.
- Adding or synchronizing a GitHub issue.

---

### Architecture Baselines Agent System Layered Runtime Baseline.Json (`docs/architecture/baselines/agent-system-layered-runtime-baseline.json`)

{
  "schemaVersion": 2,
  "goal": "agent-system-layered-runtime",
  "headCommit": "9db26c0d788b40c1a48fc114a1a7ce23f0c4c6d5",
  "relevantWorkingTree": {
    "dirty": false,
    "files": []
  },
  "sourceRoots": [
    "src/main/agent/shared",
    "src/main/agent/manager",
    "src/main/agent/deepchat",
    "src/main/agent/acp",
    "src/shared/contracts"
  ],
  "sourceFiles": [
    "src/main/agent/acp/acpAgentRepository.ts",
    "src/main/agent/acp/auth/acpAuthService.ts",
    "src/main/agent/acp/auth/acpTerminalAuthRunner.ts",
    "src/main/agent/acp/catalog/acpRegistryConstants.ts",
    "src/main/agent/acp/catalog/acpRegistryMigrationService.ts",
    "src/main/agent/acp/catalog/acpRegistryService.ts",
    "src/main/agent/acp/catalog/data/settingsTable.ts",
    "src/main/agent/acp/catalog/settings.ts",
    "src/main/agent/acp/catalog/settingsDbStore.ts",
    "src/main/agent/acp/client/acpRuntimeOwner.ts",
    "src/main/agent/acp/client/connection/AcpConnectionManager.ts",
    "src/main/agent/acp/client/index.ts",
    "src/main/agent/acp/client/session/AcpPromptController.ts",
    "src/main/agent/acp/client/session/AcpSessionRuntime.ts",
    "src/main/agent/acp/client/types.ts",
    "src/main/agent/acp/compatibility/adapters.ts",
    "src/main/agent/acp/compatibility/dependencies.ts",
    "src/main/agent/acp/createRuntimeOwner.ts",
    "src/main/agent/acp/instance/acpAgentInstance.ts",
    "src/main/agent/acp/instance/acpAgentRuntime.ts",
    "src/main/agent/acp/instance/index.ts",
    "src/main/agent/acp/instance/ports.ts",
    "src/main/agent/acp/launch/acpLaunchSpecService.ts",
    "src/main/agent/acp/routes.ts",
    "src/main/agent/acp/runtime/acpAuthentication.ts",
    "src/main/agent/acp/runtime/acpCapabilities.ts",
    "src/main/agent/acp/runtime/acpCompatibilityPromptBuilder.ts",
    "src/main/agent/acp/runtime/acpConfigState.ts",
    "src/main/agent/acp/runtime/acpConnection.ts",
    "src/main/agent/acp/runtime/acpContentMapper.ts",
    "src/main/agent/acp/runtime/acpDebugLog.ts",
    "src/main/agent/acp/runtime/acpElicitationBridge.ts",
    "src/main/agent/acp/runtime/acpExtensionState.ts",
    "src/main/agent/acp/runtime/acpFsHandler.ts",
    "src/main/agent/acp/runtime/acpHistory.ts",
    "src/main/agent/acp/runtime/acpLodyExtensions.ts",
    "src/main/agent/acp/runtime/acpMessageFormatter.ts",
    "src/main/agent/acp/runtime/acpPathGuard.ts",
    "src/main/agent/acp/runtime/acpPermissionBridge.ts",
    "src/main/agent/acp/runtime/acpProcessManager.ts",
    "src/main/agent/acp/runtime/acpSessionController.ts",
    "src/main/agent/acp/runtime/acpSessionManager.ts",
    "src/main/agent/acp/runtime/acpSessionPersistence.ts",
    "src/main/agent/acp/runtime/acpTerminalManager.ts",
    "src/main/agent/acp/runtime/index.ts",
    "src/main/agent/acp/runtime/mcpConfigConverter.ts",
    "src/main/agent/acp/runtime/mcpTransportFilter.ts",
    "src/main/agent/acp/runtime/types.ts",
    "src/main/agent/deepchat/deepChatAgentRepository.ts",
    "src/main/agent/deepchat/defaults.ts",
    "src/main/agent/deepchat/harness/createDeepChatAgentHarness.ts",
    "src/main/agent/deepchat/harness/deepChatAgentHarness.ts",
    "src/main/agent/deepchat/harness/index.ts",
    "src/main/agent/deepchat/harness/pendingInputWakeupBinding.ts",
    "src/main/agent/deepchat/harness/runtimeServices.ts",
    "src/main/agent/deepchat/instance/deepChatAgentInstance.ts",
    "src/main/agent/deepchat/instance/deepChatAgentRuntime.ts",
    "src/main/agent/deepchat/loop/contextCoordinator.ts",
    "src/main/agent/deepchat/loop/deepChatLoopEngine.ts",
    "src/main/agent/deepchat/loop/inputPreparationCoordinator.ts",
    "src/main/agent/deepchat/loop/loopRun.ts",
    "src/main/agent/deepchat/loop/notificationObserver.ts",
    "src/main/agent/deepchat/loop/ports.ts",
    "src/main/agent/deepchat/loop/providerProjectionIdentity.ts",
    "src/main/agent/deepchat/loop/providerRetryPolicy.ts",
    "src/main/agent/deepchat/memory/memoryExtractionChunks.ts",
    "src/main/agent/deepchat/memory/memoryIngestionObserver.ts",
    "src/main/agent/deepchat/memory/memoryPromptContributor.ts",
    "src/main/agent/deepchat/memory/memoryRuntimeCoordinator.ts",
    "src/main/agent/deepchat/resources/promptAssembly.ts",
    "src/main/agent/deepchat/resources/systemEnvPromptBuilder.ts",
    "src/main/agent/deepchat/resources/systemPromptBuilder.ts",
    "src/main/agent/deepchat/runtime/abortErrors.ts",
    "src/main/agent/deepchat/runtime/accumulator.ts",
    "src/main/agent/deepchat/runtime/compactionRuntimeCoordinator.ts",
    "src/main/agent/deepchat/runtime/compactionService.ts",
    "src/main/agent/deepchat/runtime/contextBudget.ts",
    "src/main/agent/deepchat/runtime/contextBudgetPolicy.ts",
    "src/main/agent/deepchat/runtime/contextBuilder.ts",
    "src/main/agent/deepchat/runtime/contextContributions.ts",
    "src/main/agent/deepchat/runtime/contextOccupancyCoordinator.ts",
    "src/main/agent/deepchat/runtime/contextWindowError.ts",
    "src/main/agent/deepchat/runtime/deepChatLoopRunner.ts",
    "src/main/agent/deepchat/runtime/deferredExecutionContract.ts",
    "src/main/agent/deepchat/runtime/deferredToolExecutor.ts",
    "src/main/agent/deepchat/runtime/deferredToolSurface.ts",
    "src/main/agent/deepchat/runtime/dispatch.ts",
    "src/main/agent/deepchat/runtime/echo.ts",
    "src/main/agent/deepchat/runtime/generationSettings.ts",
    "src/main/agent/deepchat/runtime/imageGenerationBlocks.ts",
    "src/main/agent/deepchat/runtime/interactionCoordinator.ts",
    "src/main/agent/deepchat/runtime/interactionParkingRegistry.ts",
    "src/main/agent/deepchat/runtime/interactionProjection.ts",
    "src/main/agent/deepchat/runtime/jevPermissionQuestions.ts",
    "src/main/agent/deepchat/runtime/jevPruningFeedback.ts",
    "src/main/agent/deepchat/runtime/jevReviewState.ts",
    "src/main/agent/deepchat/runtime/jevToolResultPruning.ts",
    "src/main/agent/deepchat/runtime/messageProjectionService.ts",
    "src/main/agent/deepchat/runtime/messageTracePayload.ts",
    "src/main/agent/deepchat/runtime/noProgressToolLoopGuard.ts",
    "src/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.ts",
    "src/main/agent/deepchat/runtime/pendingInputContracts.ts",
    "src/main/agent/deepchat/runtime/pendingInputPump.ts",
    "src/main/agent/deepchat/runtime/pluginContext.ts",
    "src/main/agent/deepchat/runtime/preStreamWatchdog.ts",
    "src/main/agent/deepchat/runtime/process.ts",
    "src/main/agent/deepchat/runtime/programmaticExecParent.ts",
    "src/main/agent/deepchat/runtime/programmaticToolSurface.ts",
    "src/main/agent/deepchat/runtime/promptAssemblyService.ts",
    "src/main/agent/deepchat/runtime/providerInputCapabilities.ts",
    "src/main/agent/deepchat/runtime/providerModelRuntimeFacts.ts",
    "src/main/agent/deepchat/runtime/providerPermissionCoordinator.ts",
    "src/main/agent/deepchat/runtime/providerPermissionResolution.ts",
    "src/main/agent/deepchat/runtime/providerReplaySegments.ts",
    "src/main/agent/deepchat/runtime/reviewText.ts",
    "src/main/agent/deepchat/runtime/runLifecycleCoordinator.ts",
    "src/main/agent/deepchat/runtime/runTerminalProjectionError.ts",
    "src/main/agent/deepchat/runtime/runtimeErrorLogging.ts",
    "src/main/agent/deepchat/runtime/runtimeHookSink.ts",
    "src/main/agent/deepchat/runtime/runtimeMetadata.ts",
    "src/main/agent/deepchat/runtime/sessionIdentityService.ts",
    "src/main/agent/deepchat/runtime/sessionLifecycleCoordinator.ts",
    "src/main/agent/deepchat/runtime/sessionSettingsCoordinator.ts",
    "src/main/agent/deepchat/runtime/sessionStateResolver.ts",
    "src/main/agent/deepchat/runtime/sessionStatusPublisher.ts",
    "src/main/agent/deepchat/runtime/sessionUpdates.ts",
    "src/main/agent/deepchat/runtime/skillContextMaterializer.ts",
    "src/main/agent/deepchat/runtime/streamRequestId.ts",
    "src/main/agent/deepchat/runtime/tapeViewAssembler.ts",
    "src/main/agent/deepchat/runtime/tapeViewPolicy.ts",
    "src/main/agent/deepchat/runtime/taskContractCapability.ts",
    "src/main/agent/deepchat/runtime/toolAdapters.ts",
    "src/main/agent/deepchat/runtime/toolExecutionPolicy.ts",
    "src/main/agent/deepchat/runtime/toolOutputGuard.ts",
    "src/main/agent/deepchat/runtime/toolPermissionReviewer.ts",
    "src/main/agent/deepchat/runtime/toolResolver.ts",
    "src/main/agent/deepchat/runtime/toolRuntimeBindings.ts",
    "src/main/agent/deepchat/runtime/toolSurface.ts",
    "src/main/agent/deepchat/runtime/toolSurfaceCanaryDiagnostics.ts",
    "src/main/agent/deepchat/runtime/toolSurfaceCanaryPricing.ts",
    "src/main/agent/deepchat/runtime/toolSurfaceDiagnostics.ts",
    "src/main/agent/deepchat/runtime/toolSurfaceRollout.ts",
    "src/main/agent/deepchat/runtime/toolSurfaceSelection.ts",
    "src/main/agent/deepchat/runtime/transcriptMutationCoordinator.ts",
    "src/main/agent/deepchat/runtime/turnCoordinator.ts",
    "src/main/agent/deepchat/runtime/turnResumeContract.ts",
    "src/main/agent/deepchat/runtime/types.ts",
    "src/main/agent/manager/agentManager.ts",
    "src/main/agent/manager/deepChatAgentBackend.ts",
    "src/main/agent/manager/directAcpAgentBackend.ts",
    "src/main/agent/manager/sessionHandles.ts",
    "src/main/agent/shared/agentCatalogCodec.ts",
    "src/main/agent/shared/agentCatalogEventSink.ts",
    "src/main/agent/shared/agentCompatibilityMapper.ts",
    "src/main/agent/shared/agentDescriptors.ts",
    "src/main/agent/shared/agentRowStore.ts",
    "src/main/agent/shared/agentSessionHandle.ts",
    "src/main/agent/shared/agentSessionIds.ts",
    "src/main/agent/shared/agentSessionNormalization.ts",
    "src/main/agent/shared/appSessionService.ts",
    "src/main/agent/shared/assistantModelSelection.ts",
    "src/main/agent/shared/availableAgentCatalog.ts",
    "src/main/agent/shared/process/backgroundExecLogger.ts",
    "src/main/agent/shared/process/backgroundExecSessionManager.ts",
    "src/main/agent/shared/process/backgroundExecUtilityHost.ts",
    "src/main/agent/shared/process/childProcessRegistry.ts",
    "src/main/agent/shared/process/commandShellPath.ts",
    "src/main/agent/shared/process/commandShellService.ts",
    "src/main/agent/shared/process/processTree.ts",
    "src/main/agent/shared/process/shellEnvHelper.ts",
    "src/main/agent/shared/process/shellOutputEncoding.ts",
    "src/main/agent/shared/process/spawnGuard.ts",
    "src/main/agent/shared/storage/sessionPaths.ts",
    "src/main/provider/providers/acpProvider.ts",
    "src/main/session/assignment.ts",
    "src/main/session/data/transcript.ts",
    "src/main/session/lifecycle.ts",
    "src/main/session/query.ts",
    "src/main/session/turn.ts",
    "src/main/tape/application/sessionTape.ts",
    "src/main/tape/ports/capabilities.ts"
  ],
  "expectedFiles": {
    "src/main/agent/acp/instance/acpAgentInstance.ts": true,
    "src/main/agent/acp/instance/acpAgentRuntime.ts": true,
    "src/main/agent/deepchat/harness/createDeepChatAgentHarness.ts": true,
    "src/main/agent/deepchat/harness/deepChatAgentHarness.ts": true,
    "src/main/agent/deepchat/instance/deepChatAgentInstance.ts": true,
    "src/main/agent/deepchat/instance/deepChatAgentRuntime.ts": true,
    "src/main/agent/deepchat/loop/deepChatLoopEngine.ts": true,
    "src/main/agent/deepchat/loop/ports.ts": true,
    "src/main/agent/deepchat/memory/memoryIngestionObserver.ts": true,
    "src/main/agent/deepchat/memory/memoryPromptContributor.ts": true,
    "src/main/agent/deepchat/memory/memoryRuntimeCoordinator.ts": true,
    "src/main/agent/deepchat/runtime/compactionRuntimeCoordinator.ts": true,
    "src/main/agent/deepchat/runtime/dispatch.ts": true,
    "src/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.ts": true,
    "src/main/agent/deepchat/runtime/pendingInputPump.ts": true,
    "src/main/agent/deepchat/runtime/process.ts": true,
    "src/main/agent/deepchat/runtime/runLifecycleCoordinator.ts": true,
    "src/main/agent/deepchat/runtime/runtimeHookSink.ts": true,
    "src/main/agent/deepchat/runtime/sessionSettingsCoordinator.ts": true,
    "src/main/agent/deepchat/runtime/sessionStatusPublisher.ts": true,
    "src/main/agent/deepchat/runtime/turnCoordinator.ts": true,
    "src/main/agent/manager/agentManager.ts": true,
    "src/main/agent/manager/deepChatAgentBackend.ts": true,
    "src/main/agent/manager/directAcpAgentBackend.ts": true,
    "src/main/agent/manager/sessionHandles.ts": true,
    "src/main/agent/shared/agentCatalogCodec.ts": true,
    "src/main/agent/shared/agentDescriptors.ts": true,
    "src/main/agent/shared/appSessionService.ts": true,
    "src/main/provider/providers/acpProvider.ts": true,
    "src/main/session/assignment.ts": true,
    "src/main/session/data/transcript.ts": true,
    "src/main/session/lifecycle.ts": true,
    "src/main/session/query.ts": true,
    "src/main/session/turn.ts": true,
    "src/main/tape/application/sessionTape.ts": true,
    "src/main/tape/ports/capabilities.ts": true
  },
  "ownerEvidence": {
    "agentManager": {
      "file": "src/main/agent/manager/agentManager.ts",
      "exists": true,
      "declarationCount": 1
    },
    "typedDeepChatBackend": {
      "file": "src/main/agent/manager/deepChatAgentBackend.ts",
      "exists": true,
      "declarationCount": 1
    },
    "directAcpBackend": {
      "file": "src/main/agent/manager/directAcpAgentBackend.ts",
      "exists": true,
      "declarationCount": 1
    },
    "deepChatRuntime": {
      "file": "src/main/agent/deepchat/instance/deepChatAgentRuntime.ts",
      "exists": true,
      "declarationCount": 1
    },
    "deepChatInstance": {
      "file": "src/main/agent/deepchat/instance/deepChatAgentInstance.ts",
      "exists": true,
      "declarationCount": 1
    },
    "deepChatLoopEngine": {
      "file": "src/main/agent/deepchat/loop/deepChatLoopEngine.ts",
      "exists": true,
      "declarationCount": 1
    },
    "tapeToolFactWriter": {
      "file": "src/main/tape/ports/capabilities.ts",
      "exists": true,
      "declarationCount": 1
    },
    "memoryRuntimeCoordinator": {
      "file": "src/main/agent/deepchat/memory/memoryRuntimeCoordinator.ts",
      "exists": true,
      "declarationCount": 1
    },
    "memoryPromptContributor": {
      "file": "src/main/agent/deepchat/memory/memoryPromptContributor.ts",
      "exists": true,
      "declarationCount": 1
    },
    "memoryIngestionObserver": {
      "file": "src/main/agent/deepchat/memory/memoryIngestionObserver.ts",
      "exists": true,
      "declarationCount": 1
    },
    "acpRuntime": {
      "file": "src/main/agent/acp/instance/acpAgentRuntime.ts",
      "exists": true,
      "declarationCount": 1
    },
    "acpInstance": {
      "file": "src/main/agent/acp/instance/acpAgentInstance.ts",
      "exists": true,
      "declarationCount": 1
    },
    "sessionQuery": {
      "file": "src/main/session/query.ts",
      "exists": true,
      "declarationCount": 1
    },
    "sessionAssignment": {
      "file": "src/main/session/assignment.ts",
      "exists": true,
      "declarationCount": 1
    },
    "sessionTurn": {
      "file": "src/main/session/turn.ts",
      "exists": true,
      "declarationCount": 1
    },
    "sessionLifecycle": {
      "file": "src/main/session/lifecycle.ts",
      "exists": true,
      "declarationCount": 1
    },
    "deepChatAgentHarness": {
      "file": "src/main/agent/deepchat/harness/deepChatAgentHarness.ts",
      "exists": true,
      "declarationCount": 1
    },
    "runLifecycleCoordinator": {
      "file": "src/main/agent/deepchat/runtime/runLifecycleCoordinator.ts",
      "exists": true,
      "declarationCount": 1
    },
    "sessionStatusPublisher": {
      "file": "src/main/agent/deepchat/runtime/sessionStatusPublisher.ts",
      "exists": true,
      "declarationCount": 1
    },
    "pendingInputAdmissionCoordinator": {
      "file": "src/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.ts",
      "exists": true,
      "declarationCount": 1
    },
    "pendingInputPump": {
      "file": "src/main/agent/deepchat/runtime/pendingInputPump.ts",
      "exists": true,
      "declarationCount": 1
    },
    "turnCoordinator": {
      "file": "src/main/agent/deepchat/runtime/turnCoordinator.ts",
      "exists": true,
      "declarationCount": 1
    },
    "compactionRuntimeCoordinator": {
      "file": "src/main/agent/deepchat/runtime/compactionRuntimeCoordinator.ts",
      "exists": true,
      "declarationCount": 1
    },
    "sessionSettingsCoordinator": {
      "file": "src/main/agent/deepchat/runtime/sessionSettingsCoordinator.ts",
      "exists": true,
      "declarationCount": 1
    },
    "runtimeHookSink": {
      "file": "src/main/agent/deepchat/runtime/runtimeHookSink.ts",
      "exists": true,
      "declarationCount": 1
    }
  },
  "retiredSurfaces": {
    "paths": {
      "src/main/agent/manager/legacyAgentBackends.ts": 0,
      "src/main/lib/agentRuntime": 0,
      "src/main/presenter/agentSessionPresenter": 0,
      "src/main/presenter/index.ts": 0,
      "src/main/presenter/lifecyclePresenter": 0,
      "src/main/presenter/sessionPresenter": 0,
      "src/shared/lifecycle.ts": 0,
      "src/shared/types/presenters/agent-session.presenter.d.ts": 0,
      "src/shared/types/presenters/session.presenter.d.ts": 0
    },
    "symbols": {
      "AgentRegistry": 0,
      "AgentSessionPresenter": 0,
      "IAgentSessionPresenter": 0,
      "IAgentImplementation": 0,
      "createLegacyAgentBackend": 0,
      "LegacyDeepChatSessionBackend": 0,
      "LegacyAcpSessionBackend": 0,
      "LegacyAcpSessionHandle": 0,
      "LegacyToolFactsSnapshotPort": 0,
      "appendAssistantToolFactsSnapshot": 0,
      "agentHandleLegacyDirectRuntimeKind": 0
    }
  },
  "runtimeOwnership": {
    "deepchat": {
      "runtime": "src/main/agent/deepchat/instance/deepChatAgentRuntime.ts",
      "instance": "src/main/agent/deepchat/instance/deepChatAgentInstance.ts",
      "loopEngine": "src/main/agent/deepchat/loop/deepChatLoopEngine.ts",
      "backend": "src/main/agent/manager/deepChatAgentBackend.ts"
    },
    "acp": {
      "runtime": "src/main/agent/acp/instance/acpAgentRuntime.ts",
      "instance": "src/main/agent/acp/instance/acpAgentInstance.ts",
      "backend": "src/main/agent/manager/directAcpAgentBackend.ts"
    },
    "memory": {
      "coordinator": "src/main/agent/deepchat/memory/memoryRuntimeCoordinator.ts",
      "promptContributor": "src/main/agent/deepchat/memory/memoryPromptContributor.ts",
      "ingestionObserver": "src/main/agent/deepchat/memory/memoryIngestionObserver.ts"
    },
    "runtimeBoundaries": [
      "src/main/agent/deepchat/harness/createDeepChatAgentHarness.ts",
      "src/main/agent/deepchat/harness/deepChatAgentHarness.ts",
      "src/main/agent/deepchat/runtime/compactionRuntimeCoordinator.ts",
      "src/main/agent/deepchat/runtime/dispatch.ts",
      "src/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.ts",
      "src/main/agent/deepchat/runtime/pendingInputPump.ts",
      "src/main/agent/deepchat/runtime/process.ts",
      "src/main/agent/deepchat/runtime/runLifecycleCoordinator.ts",
      "src/main/agent/deepchat/runtime/runtimeHookSink.ts",
      "src/main/agent/deepchat/runtime/sessionSettingsCoordinator.ts",
      "src/main/agent/deepchat/runtime/sessionStatusPublisher.ts",
      "src/main/agent/deepchat/runtime/turnCoordinator.ts",
      "src/main/provider/providers/acpProvider.ts",
      "src/main/session/assignment.ts",
      "src/main/session/data/transcript.ts",
      "src/main/session/lifecycle.ts",
      "src/main/session/query.ts",
      "src/main/session/turn.ts",
      "src/main/tape/application/sessionTape.ts",
      "src/main/tape/ports/capabilities.ts"
    ]
  },
  "contracts": {
    "files": [
      "src/shared/contracts/events/acp-auth.events.ts",
      "src/shared/contracts/events/acp-extensions.events.ts",
      "src/shared/contracts/events/app-runtime.events.ts",
      "src/shared/contracts/events/approvals.events.ts",
      "src/shared/contracts/events/browser.events.ts",
      "src/shared/contracts/events/chat.events.ts",
      "src/shared/contracts/events/computerUse.events.ts",
      "src/shared/contracts/events/config.events.ts",
      "src/shared/contracts/events/context-menu.events.ts",
      "src/shared/contracts/events/dialog.events.ts",
      "src/shared/contracts/events/knowledge.events.ts",
      "src/shared/contracts/events/mcp.events.ts",
      "src/shared/contracts/events/memory.events.ts",
      "src/shared/contracts/events/misc.providers.events.ts",
      "src/shared/contracts/events/models.events.ts",
      "src/shared/contracts/events/notification.events.ts",
      "src/shared/contracts/events/oauth.events.ts",
      "src/shared/contracts/events/orchestration.events.ts",
      "src/shared/contracts/events/project.events.ts",
      "src/shared/contracts/events/providers.events.ts",
      "src/shared/contracts/events/runs.events.ts",
      "src/shared/contracts/events/sessions.events.ts",
      "src/shared/contracts/events/settings.events.ts",
      "src/shared/contracts/events/skillSync.events.ts",
      "src/shared/contracts/events/skills.events.ts",
      "src/shared/contracts/events/startup.events.ts",
      "src/shared/contracts/events/sync.events.ts",
      "src/shared/contracts/events/toolchains.events.ts",
      "src/shared/contracts/events/upgrade.events.ts",
      "src/shared/contracts/events/window.events.ts",
      "src/shared/contracts/events/workspace.events.ts",
      "src/shared/contracts/routes/acp-auth.routes.ts",
      "src/shared/contracts/routes/acp-extensions.routes.ts",
      "src/shared/contracts/routes/approvals.routes.ts",
      "src/shared/contracts/routes/artifacts.routes.ts",
      "src/shared/contracts/routes/audio.routes.ts",
      "src/shared/contracts/routes/browser.routes.ts",
      "src/shared/contracts/routes/chat.routes.ts",
      "src/shared/contracts/routes/cli.routes.ts",
      "src/shared/contracts/routes/computerUse.routes.ts",
      "src/shared/contracts/routes/config.routes.ts",
      "src/shared/contracts/routes/cronJobs.routes.ts",
      "src/shared/contracts/routes/database-security.routes.ts",
      "src/shared/contracts/routes/debug.routes.ts",
      "src/shared/contracts/routes/device.routes.ts",
      "src/shared/contracts/routes/dialog.routes.ts",
      "src/shared/contracts/routes/file.routes.ts",
      "src/shared/contracts/routes/knowledge.routes.ts",
      "src/shared/contracts/routes/mcp.routes.ts",
      "src/shared/contracts/routes/media.routes.ts",
      "src/shared/contracts/routes/memory.routes.ts",
      "src/shared/contracts/routes/models.routes.ts",
      "src/shared/contracts/routes/notification.routes.ts",
      "src/shared/contracts/routes/nowledgeMem.routes.ts",
      "src/shared/contracts/routes/oauth.routes.ts",
      "src/shared/contracts/routes/ocr.routes.ts",
      "src/shared/contracts/routes/onboarding.routes.ts",
      "src/shared/contracts/routes/orchestration.routes.ts",
      "src/shared/contracts/routes/performance.routes.ts",
      "src/shared/contracts/routes/plugins.routes.ts",
      "src/shared/contracts/routes/project.routes.ts",
      "src/shared/contracts/routes/providers.routes.ts",
      "src/shared/contracts/routes/remote-control.routes.ts",
      "src/shared/contracts/routes/runs.routes.ts",
      "src/shared/contracts/routes/sessions.routes.ts",
      "src/shared/contracts/routes/settings.routes.ts",
      "src/shared/contracts/routes/shortcut.routes.ts",
      "src/shared/contracts/routes/skillSync.routes.ts",
      "src/shared/contracts/routes/skills.routes.ts",
      "src/shared/contracts/routes/startup.routes.ts",
      "src/shared/contracts/routes/sync.routes.ts",
      "src/shared/contracts/routes/syncHost.routes.ts",
      "src/shared/contracts/routes/syncPeer.routes.ts",
      "src/shared/contracts/routes/system.routes.ts",
      "src/shared/contracts/routes/tab.routes.ts",
      "src/shared/contracts/routes/toolchains.routes.ts",
      "src/shared/contracts/routes/tools.routes.ts",
      "src/shared/contracts/routes/upgrade.routes.ts",
      "src/shared/contracts/routes/window.routes.ts",
      "src/shared/contracts/routes/workspace.routes.ts"
    ],
    "sha256": "c9dbfcd3af962dca818f3b78fed0a81bd5aa91fc2abb0dc1cead48c130ba14c6"
  },
  "storage": {
    "sqlite": {
      "files": [
        "src/main/data/schemaCatalog.ts",
        "src/main/data/schemaCatalogMetadata.ts",
        "src/main/data/schemaTypes.ts"
      ],
      "tableIdentifiers": [],
      "sha256": "4565f6894a783b99af92bb1f33c877eb1936e3cf4c69a545eb9e5cee44f47ac0"
    },
    "memoryDuckDbSidecar": {
      "files": [
        "src/main/memory/infra/memoryVectorStore.ts"
      ],
      "tableIdentifiers": [
        "embedding_meta",
        "memory_vector"
      ],
      "versionContract": "embedding identity stored in embedding_meta; no numeric schema version",
      "sha256": "1f1e48a568223a67f1dc55e3901728fbefb3f741427882bde941d98ddccefc56"
    }
  },
  "compositionAndShutdown": {
    "files": [
      "src/main/app/composition.ts",
      "src/main/app/mainProcess.ts",
      "src/main/appMain.ts"
    ],
    "sha256": "40c6f42127aa5d3fb30e0e93c72403743d1b1aa83c44918edf2d9455f6bd2647"
  },
  "dependencyMetrics": {
    "loopFiles": [
      "src/main/agent/deepchat/loop/contextCoordinator.ts",
      "src/main/agent/deepchat/loop/deepChatLoopEngine.ts",
      "src/main/agent/deepchat/loop/inputPreparationCoordinator.ts",
      "src/main/agent/deepchat/loop/loopRun.ts",
      "src/main/agent/deepchat/loop/notificationObserver.ts",
      "src/main/agent/deepchat/loop/ports.ts",
      "src/main/agent/deepchat/loop/providerProjectionIdentity.ts",
      "src/main/agent/deepchat/loop/providerRetryPolicy.ts"
    ],
    "loopToPresenter": 0,
    "loopToSqlite": 0,
    "loopToElectron": 0,
    "loopToRoutes": 0,
    "loopToAcp": 0
  }
}

---

### Architecture Baselines Dependency Report (`docs/architecture/baselines/dependency-report.md`)

# Dependency Baseline

Generated on 2026-09-06.

## main

- Total files: 862
- Internal dependency edges: 3035
- Cycles detected: 14

### Top outgoing dependencies

- `app/composition.ts`: 183
- `agent/deepchat/runtime/deepChatLoopRunner.ts`: 61
- `agent/deepchat/runtime/turnCoordinator.ts`: 53
- `data/schemaCatalog.ts`: 46
- `agent/deepchat/harness/createDeepChatAgentHarness.ts`: 42
- `tool/agentTools/agentToolManager.ts`: 37
- `agent/deepchat/harness/runtimeServices.ts`: 35
- `agent/deepchat/runtime/dispatch.ts`: 27
- `agent/deepchat/runtime/interactionCoordinator.ts`: 27
- `agent/deepchat/runtime/compactionRuntimeCoordinator.ts`: 24
- `session/data/database.ts`: 24
- `tool/index.ts`: 24
- `memory/index.ts`: 23
- `tape/application/sessionTape.ts`: 22
- `agent/deepchat/runtime/deferredToolExecutor.ts`: 20

### Top incoming dependencies

- `provider/settings.ts`: 49
- `agent/shared/agentSessionIds.ts`: 46
- `data/baseTable.ts`: 45
- `routes/routeRegistry.ts`: 43
- `remote/types.ts`: 39
- `tape/ports/capabilities.ts`: 36
- `agent/settings.ts`: 35
- `config/settingsStore.ts`: 35
- `tape/domain/entry.ts`: 35
- `tape/domain/canonicalJson.ts`: 31
- `agent/deepchat/runtime/types.ts`: 28
- `agent/deepchat/runtime/toolSurface.ts`: 25
- `tape/domain/executionJournal.ts`: 25
- `memory/types.ts`: 24
- `agent/deepchat/instance/deepChatAgentRuntime.ts`: 23

### Cycle samples

- `session/data/tables/deepchatAssistantBlocks.ts -> session/data/messageContent.ts -> session/data/tables/deepchatAssistantBlocks.ts`
- `memory/types.ts -> memory/injection.ts -> memory/core/injectionPort.ts -> memory/types.ts`
- `memory/core/injectionPort.ts -> memory/core/directiveContribution.ts -> memory/core/injectionPort.ts`
- `agent/acp/runtime/acpProcessManager.ts -> agent/deepchat/runtime/types.ts -> session/contracts.ts -> agent/manager/sessionHandles.ts -> agent/acp/instance/index.ts -> agent/acp/instance/acpAgentInstance.ts -> agent/acp/runtime/acpSessionManager.ts -> agent/acp/runtime/acpProcessManager.ts`
- `agent/acp/runtime/index.ts -> agent/acp/runtime/acpProcessManager.ts -> agent/deepchat/runtime/types.ts -> session/contracts.ts -> agent/manager/sessionHandles.ts -> agent/acp/instance/index.ts -> agent/acp/instance/acpAgentInstance.ts -> agent/acp/runtime/acpPermissionBridge.ts -> agent/acp/instance/ports.ts -> agent/acp/runtime/index.ts`
- `agent/acp/client/index.ts -> agent/acp/runtime/index.ts -> agent/acp/runtime/acpProcessManager.ts -> agent/deepchat/runtime/types.ts -> session/contracts.ts -> agent/manager/sessionHandles.ts -> agent/acp/instance/index.ts -> agent/acp/instance/acpAgentRuntime.ts -> agent/acp/client/index.ts`
- `agent/acp/runtime/index.ts -> agent/acp/runtime/acpProcessManager.ts -> agent/deepchat/runtime/types.ts -> session/contracts.ts -> agent/manager/sessionHandles.ts -> agent/acp/runtime/index.ts`
- `agent/acp/client/index.ts -> agent/acp/client/acpRuntimeOwner.ts -> agent/acp/client/index.ts`
- `desktop/browser/YoBrowserPresenter.ts -> desktop/browser/YoBrowserToolHandler.ts -> desktop/browser/YoBrowserPresenter.ts`
- `tool/agentTools/agentToolManager.ts -> tool/agentTools/agentTapeTools.ts -> tool/agentTools/agentToolManager.ts`
- `tool/agentTools/agentToolManager.ts -> tool/agentTools/agentMemoryTools.ts -> tool/agentTools/agentToolManager.ts`
- `tool/agentTools/agentToolManager.ts -> tool/agentTools/cronJobTool.ts -> tool/agentTools/agentToolManager.ts`
- `tool/agentTools/agentToolManager.ts -> tool/agentTools/liveDelegationTool.ts -> tool/agentTools/agentToolManager.ts`
- `skill/sync/toolScanner.ts -> skill/sync/security.ts -> skill/sync/toolScanner.ts`

## renderer-main

- Total files: 354
- Internal dependency edges: 664
- Cycles detected: 2

### Top outgoing dependencies

- `features/chat-page/ChatPage.vue`: 45
- `apps/chat-main/ChatMainApp.vue`: 32
- `i18n/index.ts`: 21
- `components/message/MessageItemAssistant.vue`: 20
- `pages/NewThreadPage.vue`: 20
- `components/chat/ChatStatusBar.vue`: 19
- `components/WindowSideBar.vue`: 16
- `apps/chat-main/ChatTabView.vue`: 15
- `components/tape-inspector/TapeInspectorPanel.vue`: 11
- `components/chat/ChatInputBox.vue`: 10
- `features/chat-page/composables/useComposerSubmit.ts`: 10
- `stores/ui/session.ts`: 10
- `components/ChatConfig.vue`: 9
- `components/sidepanel/WorkspacePanel.vue`: 9
- `pages/plugins/SkillsPluginsPage.vue`: 9

### Top incoming dependencies

- `stores/ui/session.ts`: 33
- `features/chat-page/model/displayMessage.ts`: 32
- `stores/theme.ts`: 16
- `stores/ui/agent.ts`: 15
- `stores/ui/sidepanel.ts`: 14
- `stores/uiSettingsStore.ts`: 14
- `stores/artifact.ts`: 13
- `stores/providerStore.ts`: 13
- `stores/modelStore.ts`: 12
- `stores/ui/message.ts`: 10
- `stores/mcp.ts`: 8
- `stores/ui/project.ts`: 8
- `components/tape-inspector/model.ts`: 7
- `lib/onboardingResume.ts`: 7
- `components/icons/ModelIcon.vue`: 6

### Cycle samples

- `components/json-viewer/JsonValue.ts -> components/json-viewer/JsonObject.ts -> components/json-viewer/JsonValue.ts`
- `components/json-viewer/JsonArray.ts -> components/json-viewer/JsonValue.ts -> components/json-viewer/JsonArray.ts`

## renderer-settings

- Total files: 101
- Internal dependency edges: 165
- Cycles detected: 0

### Top outgoing dependencies

- `settingsRouteComponents.ts`: 21
- `components/ModelProviderSettingsDetail.vue`: 11
- `components/MemorySettings.vue`: 10
- `components/KnowledgeBaseSettings.vue`: 8
- `App.vue`: 6
- `components/MemoryListView.vue`: 6
- `components/ModelProviderSettings.vue`: 6
- `components/CommonSettings.vue`: 5
- `components/DataSettings.vue`: 5
- `components/BedrockProviderSettingsDetail.vue`: 4
- `components/MemoryDiagnosticsPanel.vue`: 4
- `components/MemoryDirectivesPanel.vue`: 4
- `components/MemoryInlinePanel.vue`: 4
- `components/SettingsOverview.vue`: 4
- `components/MemoryConfigInlinePanel.vue`: 3

### Top incoming dependencies

- `services/settingsLeaveGuard.ts`: 26
- `components/control-center/SettingsPageShell.vue`: 15
- `lib/useMemoryInlineFeedback.ts`: 9
- `components/MemoryInlineFeedback.vue`: 8
- `components/memoryRedesignUtils.ts`: 5
- `components/ProviderCustomHeadersEditor.vue`: 3
- `components/control-center/SettingsSectionCard.vue`: 3
- `lib/useExternalKnowledgeConfigs.ts`: 3
- `components/ProviderDialogContainer.vue`: 2
- `components/ProviderModelManager.vue`: 2
- `components/ProviderRateLimitConfig.vue`: 2
- `components/ProviderSettingsShell.vue`: 2
- `lib/guidedOnboardingSettings.ts`: 2
- `lib/useKnowledgeConfigOperation.ts`: 2
- `lib/useMemoryNumberFormatters.ts`: 2

### Cycle samples

- None

## renderer-shared

- Total files: 15
- Internal dependency edges: 38
- Cycles detected: 0

### Top outgoing dependencies

- `notifications/index.ts`: 7
- `notifications/notificationManager.ts`: 7
- `notifications/notificationArbitration.ts`: 3
- `notifications/notificationEntry.ts`: 3
- `notifications/rendererNotificationPort.ts`: 3
- `notifications/rendererNotificationRuntime.ts`: 3
- `notifications/semanticNotificationController.ts`: 3
- `notifications/sonnerNotificationPresenter.ts`: 3
- `notifications/notificationPresenter.ts`: 2
- `notifications/ManagedNotificationToast.vue`: 1
- `notifications/notificationPolicy.ts`: 1
- `notifications/notificationRecord.ts`: 1
- `notifications/notificationRequest.ts`: 1
- `notifications/NotificationHost.vue`: 0
- `notifications/notificationTypes.ts`: 0

### Top incoming dependencies

- `notifications/notificationTypes.ts`: 10
- `notifications/notificationRecord.ts`: 6
- `notifications/notificationManager.ts`: 4
- `notifications/notificationPolicy.ts`: 4
- `notifications/notificationPresenter.ts`: 4
- `notifications/notificationRequest.ts`: 3
- `notifications/notificationEntry.ts`: 2
- `notifications/ManagedNotificationToast.vue`: 1
- `notifications/notificationArbitration.ts`: 1
- `notifications/rendererNotificationRuntime.ts`: 1
- `notifications/semanticNotificationController.ts`: 1
- `notifications/sonnerNotificationPresenter.ts`: 1
- `notifications/NotificationHost.vue`: 0
- `notifications/index.ts`: 0
- `notifications/rendererNotificationPort.ts`: 0

### Cycle samples

- None

---

### Architecture Baselines Main Kernel Boundary Baseline (`docs/architecture/baselines/main-kernel-boundary-baseline.md`)

# Main Kernel Boundary Baseline

Generated on 2026-09-06.
Current phase: P5.

## Metric Snapshot

| Metric | Value |
| --- | --- |
| `renderer.usePresenter.count` | 0 |
| `renderer.business.usePresenter.count` | 0 |
| `renderer.quarantine.usePresenter.count` | 0 |
| `renderer.windowElectron.count` | 0 |
| `renderer.business.windowElectron.count` | 0 |
| `renderer.quarantine.windowElectron.count` | 0 |
| `renderer.windowApi.count` | 0 |
| `renderer.business.windowApi.count` | 0 |
| `renderer.quarantine.windowApi.count` | 0 |
| `renderer.quarantine.sourceFile.count` | 0 |
| `hotpath.directEdge.count` | 2 |
| `runtime.rawTimer.count` | 246 |
| `migrated.rawChannel.count` | 0 |
| `bridge.active.count` | 0 |
| `bridge.expired.count` | 0 |

## Renderer Single-Track Split

- Business layer: `src/renderer/src/**`, `src/renderer/settings/**`
- Retired quarantine layer: `src/renderer/api/legacy/**` must remain deleted

| Legacy surface | Business layer | Quarantine layer | Total |
| --- | --- | --- | --- |
| legacy presenter helper | 0 | 0 | 0 |
| `window.electron` | 0 | 0 | 0 |
| `window.api` | 0 | 0 | 0 |

## Quarantine Exit Snapshot

- Retained capability family: none; `renderer legacy transport` is retired
- Source files: 0 / 0
- Delete condition: already satisfied; a recreated quarantine directory is a regression.

- None

## Phase Gates

| Phase | Gate indicator | Current signal | Status |
| --- | --- | --- | --- |
| `P0` | Retired quarantine path `src/renderer/api/legacy/**` must remain deleted and baseline emits business/retired split metrics | `src/renderer/api/legacy/**` deleted; split metrics emitted | ready |
| `P1` | Business layer direct legacy presenter helper / `window.electron` / `window.api` counts must reach `0` | legacyPresenter=0, window.electron=0, window.api=0 | ready |
| `P2` | Business layer `configPresenter` and `providerRuntime` hits must reach `0` | configPresenter=0, providerRuntime=0 | ready |
| `P3` | Business layer window/device/workspace/project/file/browser/tab presenter hits must reach `0` | window=0, device=0, workspace=0, project=0, file=0, browser=0, tab=0 | ready |
| `P4` | Business layer session residual / skill / mcp / sync / upgrade / dialog / tool presenter hits must reach `0` | agentSession=0, skill=0, mcp=0, sync=0, upgrade=0, dialog=0, tool=0 | ready |
| `P5` | Business layer direct legacy access must be `0`, and retired quarantine source files must stay at `0` | businessLegacy=0/0/0, quarantineSourceFiles=0/0 | ready |

## Hot Path Direct Dependencies

- Direct edge count: 2

- `src/main/app/composition.ts -> src/main/provider/index.ts`
- `src/main/app/composition.ts -> src/main/routes/index.ts`

## Renderer legacy presenter helpers

- Total count: 0

- None

## Renderer window.electron

- Total count: 0

- None

## Renderer window.api

- Total count: 0

- None

## Raw Timers

- Total count: 246

- `src/main/ocr/lightOcrProcessHost.ts`: 10
- `src/main/tool/codeMode/runCodeRuntimeManager.ts`: 8
- `src/main/provider/auth/githubCopilotDeviceFlow.ts`: 6
- `src/main/agent/shared/process/backgroundExecSessionManager.ts`: 5
- `src/main/desktop/browser/BrowserTab.ts`: 5
- `src/main/provider/aiSdk/runtime.ts`: 5
- `src/main/remote/index.ts`: 5
- `src/main/app/splashWindow.ts`: 4
- `src/main/cli/server.ts`: 4
- `src/main/memory/infra/vectorStoreManager.ts`: 4
- `src/main/memory/services/maintenanceService.ts`: 4
- `src/main/tape/application/traceInspectorHeadWatcher.ts`: 4

## Migrated Path Raw Channel Literals

- Total count: 0

- None

---

### Architecture Baselines Main Kernel Bridge Register.Json (`docs/architecture/baselines/main-kernel-bridge-register.json`)

{
  "program": "main-kernel-refactor",
  "currentPhase": "P5",
  "updatedOn": "2026-04-20",
  "bridges": []
}

---

### Architecture Baselines Main Kernel Bridge Register (`docs/architecture/baselines/main-kernel-bridge-register.md`)

# Main Kernel Bridge Register

Updated on 2026-04-20.
Current phase: P5.

- Active bridges: 0
- Expired bridges: 0

## Entries

- None

---

### Architecture Baselines Main Kernel Migration Scoreboard.Json (`docs/architecture/baselines/main-kernel-migration-scoreboard.json`)

{
  "program": "main-kernel-refactor",
  "generatedOn": "2026-09-06",
  "currentPhase": "P5",
  "metrics": {
    "renderer.usePresenter.count": 0,
    "renderer.business.usePresenter.count": 0,
    "renderer.quarantine.usePresenter.count": 0,
    "renderer.windowElectron.count": 0,
    "renderer.business.windowElectron.count": 0,
    "renderer.quarantine.windowElectron.count": 0,
    "renderer.windowApi.count": 0,
    "renderer.business.windowApi.count": 0,
    "renderer.quarantine.windowApi.count": 0,
    "renderer.quarantine.sourceFile.count": 0,
    "hotpath.directEdge.count": 2,
    "runtime.rawTimer.count": 246,
    "migrated.rawChannel.count": 0,
    "bridge.active.count": 0,
    "bridge.expired.count": 0
  },
  "phaseGates": [
    {
      "phase": "P0",
      "indicator": "Retired quarantine path `src/renderer/api/legacy/**` must remain deleted and baseline emits business/retired split metrics",
      "current": "`src/renderer/api/legacy/**` deleted; split metrics emitted",
      "status": "ready"
    },
    {
      "phase": "P1",
      "indicator": "Business layer direct legacy presenter helper / `window.electron` / `window.api` counts must reach `0`",
      "current": "legacyPresenter=0, window.electron=0, window.api=0",
      "status": "ready"
    },
    {
      "phase": "P2",
      "indicator": "Business layer `configPresenter` and `providerRuntime` hits must reach `0`",
      "current": "configPresenter=0, providerRuntime=0",
      "status": "ready"
    },
    {
      "phase": "P3",
      "indicator": "Business layer window/device/workspace/project/file/browser/tab presenter hits must reach `0`",
      "current": "window=0, device=0, workspace=0, project=0, file=0, browser=0, tab=0",
      "status": "ready"
    },
    {
      "phase": "P4",
      "indicator": "Business layer session residual / skill / mcp / sync / upgrade / dialog / tool presenter hits must reach `0`",
      "current": "agentSession=0, skill=0, mcp=0, sync=0, upgrade=0, dialog=0, tool=0",
      "status": "ready"
    },
    {
      "phase": "P5",
      "indicator": "Business layer direct legacy access must be `0`, and retired quarantine source files must stay at `0`",
      "current": "businessLegacy=0/0/0, quarantineSourceFiles=0/0",
      "status": "ready"
    }
  ],
  "hotPathEdges": [
    "src/main/app/composition.ts -> src/main/provider/index.ts",
    "src/main/app/composition.ts -> src/main/routes/index.ts"
  ],
  "migratedRawChannels": {}
}

---

### Architecture Baselines Main Kernel Migration Scoreboard (`docs/architecture/baselines/main-kernel-migration-scoreboard.md`)

# Main Kernel Migration Scoreboard

Generated on 2026-09-06.
Current phase: P5.

Phase 0 establishes the comparison baseline. Later phases should update this report and compare against this checkpoint.

| Metric | Value | Status |
| --- | --- | --- |
| `renderer.usePresenter.count` | 0 | baseline |
| `renderer.business.usePresenter.count` | 0 | baseline |
| `renderer.quarantine.usePresenter.count` | 0 | baseline |
| `renderer.windowElectron.count` | 0 | baseline |
| `renderer.business.windowElectron.count` | 0 | baseline |
| `renderer.quarantine.windowElectron.count` | 0 | baseline |
| `renderer.windowApi.count` | 0 | baseline |
| `renderer.business.windowApi.count` | 0 | baseline |
| `renderer.quarantine.windowApi.count` | 0 | baseline |
| `renderer.quarantine.sourceFile.count` | 0 | baseline |
| `hotpath.directEdge.count` | 2 | baseline |
| `runtime.rawTimer.count` | 246 | baseline |
| `migrated.rawChannel.count` | 0 | baseline |
| `bridge.active.count` | 0 | baseline |
| `bridge.expired.count` | 0 | baseline |

## Phase Gate Status

| Phase | Status | Current signal |
| --- | --- | --- |
| `P0` | ready | `src/renderer/api/legacy/**` deleted; split metrics emitted |
| `P1` | ready | legacyPresenter=0, window.electron=0, window.api=0 |
| `P2` | ready | configPresenter=0, providerRuntime=0 |
| `P3` | ready | window=0, device=0, workspace=0, project=0, file=0, browser=0, tab=0 |
| `P4` | ready | agentSession=0, skill=0, mcp=0, sync=0, upgrade=0, dialog=0, tool=0 |
| `P5` | ready | businessLegacy=0/0/0, quarantineSourceFiles=0/0 |

---

### Architecture Baselines Renderer Application Boundaries Baseline.Json (`docs/architecture/baselines/renderer-application-boundaries-baseline.json`)

{
  "schemaVersion": 2,
  "apps": [
    {
      "id": "chat-main",
      "html": "src/renderer/index.html",
      "entry": "src/renderer/src/main.ts",
      "htmlExists": true,
      "entryExists": true
    },
    {
      "id": "browser-overlay",
      "html": "src/renderer/browser-overlay/index.html",
      "entry": "src/renderer/browser-overlay/main.ts",
      "htmlExists": true,
      "entryExists": true
    },
    {
      "id": "floating",
      "html": "src/renderer/floating/index.html",
      "entry": "src/renderer/floating/main.ts",
      "htmlExists": true,
      "entryExists": true
    },
    {
      "id": "splash",
      "html": "src/renderer/splash/index.html",
      "entry": "src/renderer/splash/main.ts",
      "htmlExists": true,
      "entryExists": true
    },
    {
      "id": "settings",
      "html": "src/renderer/settings/index.html",
      "entry": "src/renderer/settings/main.ts",
      "htmlExists": true,
      "entryExists": true
    }
  ],
  "sharedServices": [
    {
      "id": "notifications",
      "root": "src/renderer/services/notifications",
      "rootExists": true
    }
  ],
  "sharedServiceToAppImports": [],
  "sharedServiceToAppImportCount": 0,
  "browser": {
    "legacyDirectoryExists": false,
    "activeOverlayDirectory": "src/renderer/browser-overlay"
  },
  "settingsToChatAppImports": [
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "../src/composables/useDeviceVersion"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "../src/composables/useFontManager"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "../src/foundation/appearance/documentAppearance"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "../src/lib/iconLoader"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "../src/lib/startupDeferred"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "../src/lib/storeInitializer"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "../src/stores/language"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "../src/stores/modelCheck"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "../src/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "@/components/settings/ModelCheckDialog.vue"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "@/stores/ollamaStore"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "@/stores/providerDeeplinkImport"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "@/stores/startupWorkloadStore"
    },
    {
      "file": "src/renderer/settings/App.vue",
      "specifier": "@/stores/theme"
    },
    {
      "file": "src/renderer/settings/components/AboutUsSettings.vue",
      "specifier": "@/components/ui/UpdateTaskCheckDialog.vue"
    },
    {
      "file": "src/renderer/settings/components/AboutUsSettings.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/AboutUsSettings.vue",
      "specifier": "@/stores/theme"
    },
    {
      "file": "src/renderer/settings/components/AboutUsSettings.vue",
      "specifier": "@/stores/upgrade"
    },
    {
      "file": "src/renderer/settings/components/AcpDebugDialog.vue",
      "specifier": "@/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/components/AcpSettings.vue",
      "specifier": "@/components/acp/AcpAuthDialog.vue"
    },
    {
      "file": "src/renderer/settings/components/AcpSettings.vue",
      "specifier": "@/components/agent/AgentTransferDialog.vue"
    },
    {
      "file": "src/renderer/settings/components/AcpSettings.vue",
      "specifier": "@/components/icons/AcpAgentIcon.vue"
    },
    {
      "file": "src/renderer/settings/components/AcpSettings.vue",
      "specifier": "@/components/mcp-config/AgentMcpSelector.vue"
    },
    {
      "file": "src/renderer/settings/components/AddCustomModelButton.vue",
      "specifier": "@/components/settings/ModelConfigDialog.vue"
    },
    {
      "file": "src/renderer/settings/components/AddProviderFlow.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/AddProviderFlow.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/components/BedrockProviderSettingsDetail.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/BedrockProviderSettingsDetail.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue",
      "specifier": "@/components/icons/ModelIcon.vue"
    },
    {
      "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue",
      "specifier": "@/components/ModelSelect.vue"
    },
    {
      "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue",
      "specifier": "@/stores/mcp"
    },
    {
      "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue",
      "specifier": "@/stores/theme"
    },
    {
      "file": "src/renderer/settings/components/common/CommandShellSettingsSection.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/common/DefaultModelSettingsSection.vue",
      "specifier": "@/components/icons/ModelIcon.vue"
    },
    {
      "file": "src/renderer/settings/components/common/DefaultModelSettingsSection.vue",
      "specifier": "@/components/ModelSelect.vue"
    },
    {
      "file": "src/renderer/settings/components/common/DefaultModelSettingsSection.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/common/DefaultModelSettingsSection.vue",
      "specifier": "@/stores/theme"
    },
    {
      "file": "src/renderer/settings/components/common/LoggingSettingsSection.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/common/LoggingSettingsSection.vue",
      "specifier": "@/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/components/common/PrivacySettingsSection.vue",
      "specifier": "@/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/components/common/ProxySettingsSection.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/common/SettingToggleRow.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/CommonSettings.vue",
      "specifier": "@/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/components/DataSettings.vue",
      "specifier": "@/lib/cloudSyncForm"
    },
    {
      "file": "src/renderer/settings/components/DataSettings.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/DataSettings.vue",
      "specifier": "@/stores/sync"
    },
    {
      "file": "src/renderer/settings/components/DebugSettings.vue",
      "specifier": "@/stores/upgrade"
    },
    {
      "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue",
      "specifier": "@/components/agent/AgentTransferDialog.vue"
    },
    {
      "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue",
      "specifier": "@/components/icons/AgentAvatar.vue"
    },
    {
      "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue",
      "specifier": "@/components/icons/ModelIcon.vue"
    },
    {
      "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue",
      "specifier": "@/components/ModelSelect.vue"
    },
    {
      "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue",
      "specifier": "@/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/components/display/FontSettingsSection.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/display/FontSettingsSection.vue",
      "specifier": "@/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/components/DisplaySettings.vue",
      "specifier": "@/stores/floatingButton"
    },
    {
      "file": "src/renderer/settings/components/DisplaySettings.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/DisplaySettings.vue",
      "specifier": "@/stores/theme"
    },
    {
      "file": "src/renderer/settings/components/DisplaySettings.vue",
      "specifier": "@/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/components/EnvironmentsSettings.vue",
      "specifier": "@/stores/ui/project"
    },
    {
      "file": "src/renderer/settings/components/GeminiSafetyConfig.vue",
      "specifier": "@/lib/gemini"
    },
    {
      "file": "src/renderer/settings/components/GitHubCopilotOAuth.vue",
      "specifier": "@/stores/modelCheck"
    },
    {
      "file": "src/renderer/settings/components/GitHubCopilotOAuth.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/components/GrokOAuth.vue",
      "specifier": "@/stores/modelCheck"
    },
    {
      "file": "src/renderer/settings/components/KnowledgeFileItem.vue",
      "specifier": "@/lib/utils"
    },
    {
      "file": "src/renderer/settings/components/McpSettings.vue",
      "specifier": "@/components/mcp-config/components/McpServers.vue"
    },
    {
      "file": "src/renderer/settings/components/McpSettings.vue",
      "specifier": "@/components/onboarding/GuidedOnboardingOverlay.vue"
    },
    {
      "file": "src/renderer/settings/components/McpSettings.vue",
      "specifier": "@/composables/useGuidedOnboardingStep"
    },
    {
      "file": "src/renderer/settings/components/McpSettings.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/McpSettings.vue",
      "specifier": "@/stores/mcp"
    },
    {
      "file": "src/renderer/settings/components/McpSettings.vue",
      "specifier": "@/stores/ui/agent"
    },
    {
      "file": "src/renderer/settings/components/McpSettings.vue",
      "specifier": "@/stores/ui/session"
    },
    {
      "file": "src/renderer/settings/components/MemoryConfigInlinePanel.vue",
      "specifier": "@/components/icons/ModelIcon.vue"
    },
    {
      "file": "src/renderer/settings/components/MemoryConfigInlinePanel.vue",
      "specifier": "@/components/ModelSelect.vue"
    },
    {
      "file": "src/renderer/settings/components/MemoryConfigInlinePanel.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettings.vue",
      "specifier": "@/components/icons/ModelIcon.vue"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettings.vue",
      "specifier": "@/components/onboarding/GuidedOnboardingOverlay.vue"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettings.vue",
      "specifier": "@/composables/useGuidedOnboardingStep"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettings.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettings.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettings.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettings.vue",
      "specifier": "@/stores/startupWorkloadStore"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettings.vue",
      "specifier": "@/stores/theme"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue",
      "specifier": "@/lib/gemini"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue",
      "specifier": "@/lib/gemini"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue",
      "specifier": "@/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/components/OllamaProviderSettingsDetail.vue",
      "specifier": "@/components/settings/ModelConfigItem.vue"
    },
    {
      "file": "src/renderer/settings/components/OllamaProviderSettingsDetail.vue",
      "specifier": "@/stores/modelCheck"
    },
    {
      "file": "src/renderer/settings/components/OllamaProviderSettingsDetail.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/OllamaProviderSettingsDetail.vue",
      "specifier": "@/stores/ollamaStore"
    },
    {
      "file": "src/renderer/settings/components/OllamaProviderSettingsDetail.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/components/OpenAICodexOAuth.vue",
      "specifier": "@/stores/modelCheck"
    },
    {
      "file": "src/renderer/settings/components/PluginsSettings.vue",
      "specifier": "@/assets/images/nowledge-mem.webp"
    },
    {
      "file": "src/renderer/settings/components/prompt/CustomPromptSettingsSection.vue",
      "specifier": "@/lib/download"
    },
    {
      "file": "src/renderer/settings/components/prompt/CustomPromptSettingsSection.vue",
      "specifier": "@/stores/prompts"
    },
    {
      "file": "src/renderer/settings/components/prompt/PromptEditorSheet.vue",
      "specifier": "@/lib/utils"
    },
    {
      "file": "src/renderer/settings/components/prompt/SystemPromptSettingsSection.vue",
      "specifier": "@/stores/systemPromptStore"
    },
    {
      "file": "src/renderer/settings/components/ProviderApiConfig.vue",
      "specifier": "@/stores/modelCheck"
    },
    {
      "file": "src/renderer/settings/components/ProviderCatalog.vue",
      "specifier": "@/components/icons/ModelIcon.vue"
    },
    {
      "file": "src/renderer/settings/components/ProviderCatalog.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/components/ProviderCatalog.vue",
      "specifier": "@/stores/theme"
    },
    {
      "file": "src/renderer/settings/components/ProviderConfigImportDialog.vue",
      "specifier": "@/lib/utils"
    },
    {
      "file": "src/renderer/settings/components/ProviderCustomHeadersEditor.vue",
      "specifier": "@/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/components/ProviderDeeplinkImportDialog.vue",
      "specifier": "@/components/icons/ModelIcon.vue"
    },
    {
      "file": "src/renderer/settings/components/ProviderDeeplinkImportDialog.vue",
      "specifier": "@/stores/theme"
    },
    {
      "file": "src/renderer/settings/components/ProviderModelList.vue",
      "specifier": "@/components/settings/ModelConfigItem.vue"
    },
    {
      "file": "src/renderer/settings/components/ProviderModelList.vue",
      "specifier": "@/composables/useAccessibilitySupport"
    },
    {
      "file": "src/renderer/settings/components/ProviderModelList.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/ProviderModelList.vue",
      "specifier": "@/stores/uiSettingsStore"
    },
    {
      "file": "src/renderer/settings/components/ProviderSettingsShell.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/components/ReleaseNotes.vue",
      "specifier": "@/stores/theme"
    },
    {
      "file": "src/renderer/settings/components/SettingsOverview.vue",
      "specifier": "@/stores/modelStore"
    },
    {
      "file": "src/renderer/settings/components/SettingsOverview.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/components/SettingsOverview.vue",
      "specifier": "@/stores/sync"
    },
    {
      "file": "src/renderer/settings/components/SettingsOverview.vue",
      "specifier": "@/stores/ui/agent"
    },
    {
      "file": "src/renderer/settings/components/ShortcutSettings.vue",
      "specifier": "@/stores/language"
    },
    {
      "file": "src/renderer/settings/components/ShortcutSettings.vue",
      "specifier": "@/stores/shortcutKey"
    },
    {
      "file": "src/renderer/settings/components/VertexProviderSettingsDetail.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/components/VoiceAIProviderConfig.vue",
      "specifier": "@/stores/providerStore"
    },
    {
      "file": "src/renderer/settings/lib/useExternalKnowledgeConfigs.ts",
      "specifier": "@/stores/mcp"
    },
    {
      "file": "src/renderer/settings/main.ts",
      "specifier": "../src/lib/iconLoader"
    },
    {
      "file": "src/renderer/settings/main.ts",
      "specifier": "@/assets/main.css"
    },
    {
      "file": "src/renderer/settings/main.ts",
      "specifier": "@/i18n/bootstrap"
    }
  ],
  "settingsToChatAppImportCount": 128
}

---

### Architecture Baselines Zero Inbound Candidates (`docs/architecture/baselines/zero-inbound-candidates.md`)

# Zero Inbound Candidates

Generated on 2026-09-06.

These files have no in-repo importers inside their scope and need manual classification before deletion.

## main

- Candidate count: 13

- `agent/shared/agentCatalogEventSink.ts`
- `backgroundExecUtilityHostEntry.ts`
- `codeModeUtilityHostEntry.ts`
- `config/aes.ts`
- `desktop/browser/BrowserContextBuilder.ts`
- `env.d.ts`
- `fileWatcherUtilityHostEntry.ts`
- `lib/system.ts`
- `lib/terminalHelper.ts`
- `lightOcrHelperEntry.ts`
- `mcp/agentMcpFilter.ts`
- `schedulerUtilityHostEntry.ts`
- `session/data/tables/attachments.ts`

## renderer-main

- Candidate count: 44

- `components/ChatConfig.vue`
- `components/ChatConfig/ConfigSwitchField.vue`
- `components/FileItem.vue`
- `components/ModelSelect.vue`
- `components/artifacts/ArtifactBlock.vue`
- `components/chat-input/SkillsPanel.vue`
- `components/chat-input/VoiceCallWidget.vue`
- `components/chat-input/composables/useAgentMcpData.ts`
- `components/chat-input/composables/useDragAndDrop.ts`
- `components/chat-input/composables/useInputHistory.ts`
- `components/chat-input/composables/useInputSettings.ts`
- `components/chat-input/composables/usePromptInputFiles.ts`
- `components/chat-input/composables/useRateLimitStatus.ts`
- `components/chat/composables/useVoiceInput.ts`
- `components/editor/mention/PromptParamsDialog.vue`
- `components/editor/mention/mention.ts`
- `components/editor/mention/slashMention.ts`
- `components/markdown/LinkNode.vue`
- `components/mcp-config/AgentMcpSelector.vue`
- `components/mcp-config/const.ts`
- `components/message/MessageActionButtons.vue`
- `components/message/MessageItemPlaceholder.vue`
- `components/message/ReferencePreview.vue`
- `components/settings/ModelConfigItem.vue`
- `composables/useArtifactCodeEditor.ts`
- `composables/useArtifactContext.ts`
- `composables/useArtifactExport.ts`
- `composables/useArtifactViewMode.ts`
- `composables/useSearchConfig.ts`
- `composables/useViewportSize.ts`
- `env.d.ts`
- `lib/cloudSyncForm.ts`
- `lib/float.cursor.ts`
- `lib/gemini.ts`
- `lib/sanitizeText.ts`
- `main.ts`
- `stores/floatingButton.ts`
- `stores/prompts.ts`
- `stores/providerDeeplinkImport.ts`
- `stores/shortcutKey.ts`
- `stores/sync.ts`
- `stores/systemPromptStore.ts`
- `types/vuedraggable.d.ts`
- `utils/maxOutputTokens.ts`

## renderer-settings

- Candidate count: 8

- `components/AcpProfileManagerDialog.vue`
- `components/common/DefaultModelSettingsSection.vue`
- `components/common/SettingToggleRow.vue`
- `components/prompt/PromptSettingsHeader.vue`
- `icons/MaximizeIcon.vue`
- `icons/MinimizeIcon.vue`
- `icons/RestoreIcon.vue`
- `main.ts`

## renderer-shared

- Candidate count: 2

- `notifications/NotificationHost.vue`
- `notifications/rendererNotificationPort.ts`

---

### Architecture Cache Aware Context Runtime Spec (`docs/architecture/cache-aware-context-runtime/spec.md`)

# Cache-Aware Context Runtime Specification

## Context

Tape is the append-only source of truth for message corrections, reconstruction anchors,
provider-visible ViewManifest records, and frozen-head fork/Subagent reads. Context projection keeps
the provider's stable prefix separate from mutable summaries, handoff state, and recalled Memory.
Those derived, untrusted values use bounded user-role contributions rather than acquiring system
instruction authority.

Prompt-cache metadata is selected for the effective provider transport. OpenAI-compatible adapters
and Bedrock cannot be assumed to consume Anthropic message-part cache metadata. Stable prefix,
retained tail, isolated summarization, and exact View lineage are complementary contracts.

## Goals

1. Emit prompt-cache controls in the exact wire shape accepted by the installed AI SDK provider
   adapters.
2. Keep deterministic instructions at the front of the request and move mutable conversation
   reconstruction into explicitly untrusted user-role contributions.
3. Preserve complete recent turns according to both the existing configured minimum and a
   model-aware token target.
4. Persist one compact provider-attempt outcome per physical attempt so the last cache-read ratio
   is observable independently from turn-wide usage aggregation.
5. Advance ViewManifest provenance without invalidating schema versions 1 and 2 or legacy policy
   identifiers.
6. Preserve existing fail-open Memory behavior, Tape append-only semantics, public provider
   configuration, and backward-compatible database reads.

## Required Invariants

### Prompt order and trust

Provider-visible context is ordered as:

1. deterministic base system instructions;
2. the authoritative first effective user fact in the current Tape incarnation, when it is not
   already the protected active owner;
3. an optional synthetic user checkpoint derived from the current summary and visible handoff
   state;
4. complete history turns after the summary cursor;
5. the active user turn with an optional untrusted Memory contribution;
6. the current user payload or partial assistant continuation.

Summary, reconstruction state, and Memory content never use the system role. Their text remains
bounded, fenced, and labeled untrusted. A resume injects Memory into the user message that owns the
target assistant; it never appends a new user message after a partial assistant. If that owner
cannot be identified, Memory is omitted. The pinned first user remains an original user fact and
does not receive the derivative-content fence.

Tool or skill refresh replaces only the deterministic leading system instructions. It does not
repeat Memory retrieval, Memory access accounting, or `memory/view_assembled` persistence.
Context-pressure recovery may replace the checkpoint and cursor, but it also reuses the original
Memory contribution.

### Cache intent and provider mapping

Every text-generation request has an internal cache intent:

- `conversation`: reusable agent-loop request;
- `isolated`: one-shot request such as rolling summarization or title generation.

Isolated requests do not receive DeepChat cache keys, explicit breakpoints, or sticky-session
identifiers. This contract does not claim that a provider with implicit caching can be forced to
disable its own platform behavior.

Conversation requests use these transports:

| Provider path                  | Cache transport                                                                  |
| ------------------------------ | -------------------------------------------------------------------------------- |
| OpenAI Chat and Responses      | Hashed `promptCacheKey`                                                          |
| Official Anthropic             | Top-level `anthropic.cacheControl`                                               |
| Amazon Bedrock                 | `providerOptions.bedrock.cachePoint` on a reusable message or system instruction |
| OpenRouter fixed Claude models | Explicit content-block `cache_control` plus hashed `session_id`                  |
| Zenmux fixed Claude models     | Explicit content-block `cache_control`                                           |

OpenAI-compatible explicit breakpoints are applied in `transformRequestBody` after the AI SDK has
created its final messages. The breakpoint precedes the last user-owned active turn, keeping
partial assistant continuations, tool calls, and tool results in the dynamic suffix. Any
DeepChat-only transport marker is removed before the HTTP body is sent. Raw Session identifiers,
prompts, credentials, headers, responses, and exception stacks are never persisted in cache
telemetry.

### Context fitting and compaction

The base system, pinned first user, checkpoint, and active turn are protected. Optional Memory is
removed before historical turns. History is removed from the oldest complete turn and tool
call/result groups are not split. If protected content still does not fit, the runtime follows its
explicit overflow failure path rather than silently dropping trusted instructions or current user
input.
Initial chat and resume selection use the same usable context length, including the provider
safety margin, as request preflight so manifest record provenance cannot describe history removed
only at the final provider boundary.

Automatic and context-pressure compaction calculate:

```text
inputBudget =
  max(0, floor((contextLength - reserveTokens - extraReserveTokens) / 1.2))

retainedTailTokenTarget =
  min(20_000, floor(inputBudget * 0.25))
```

The retained tail is selected from newest to oldest until it satisfies both the token target and
the existing `autoCompactionRetainRecentPairs` minimum. Resume always retains the active target
turn and at least that many preceding complete turns. Manual compaction retains no configured
minimum. Retained messages are derived from Tape and the summary cursor; they are not copied into
anchor state.

### Tape provenance and attempt telemetry

New context writes use policy `cache_aware_context_v2`, policy version 1, and builder
`cache-aware-v2`. The first-user manifest ref is authoritative rather than synthetic and records
the latest effective Tape source entry, message/order identity, and the hash of the exact protected
provider message. Before writing that ref, assembly also verifies a Run-local raw source-content
hash against the effective source. `cache_aware_context_v1`, `cache-aware-v1`, schema versions 1
and 2, and builder `legacy-v1` remain readable and hash-verifiable. Existing schema families may
record contribution reasons, source entry identifiers, and content hashes without copying source
text.

The initial View fixes pin presence and identity for the Run. Context-pressure reconstruction must
inherit and verify that descriptor, or explicitly preserve its absence; it never reselects a pin
from history that changed after the Run began.

Each provider stream that actually starts appends one idempotent `provider/attempt_completed`
event keyed by Session, message, request sequence, and physical attempt. Schema version 2 records
logical-round identity, request/attempt origin, terminal and retry classification, the last
cumulative usage snapshot, and a cache-read ratio only when the provider supplied valid input and
cache-read counts; schema version 1 remains readable. An attempt canceled during rate-gate waiting
is not recorded because the provider was never called. A resumed run initializes its request
sequence from the maximum persisted manifest, request trace, and provider-attempt event so
independent fail-open writes cannot cause an idempotency collision. Its first new request still
records the supplied chat or resume selection provenance; absolute request-sequence values do not
reclassify it as a tool loop. Tape append failure is fail-open for generation.

Within one logical round, a transient retry reuses the exact payload and ViewManifest, keeps the
request sequence, and advances only the physical attempt. Context recovery changes the payload,
therefore writes a new manifest and resets the physical attempt without consuming the shared
two-retry budget. Transparent replay is forbidden after the first projected semantic event. Failed
attempt usage and final-attempt usage are checked and aggregated into message metadata, while each
Tape outcome remains attempt-local. ACP, image, video, and TTS requests do not participate in this
transparent replay policy.

Message traces add nullable logical-round and physical-attempt identity. Existing rows and ACP
traces remain valid. Replay chooses the greatest physical attempt for a requested sequence and uses
creation time plus trace ID as a stable tie-breaker.

## Acceptance Criteria

1. Real installed AI SDK adapters produce the expected final OpenAI, Anthropic,
   OpenAI-compatible, and Bedrock request shapes.
2. No internal cache marker reaches a captured HTTP body.
3. One-shot generation receives no DeepChat cache opt-in.
4. Recalled Memory, summary text, and handoff state are never present in system instructions.
5. Repeated turns with unchanged resources produce a byte-identical stable prefix; dynamic Memory
   changes only the active user turn.
6. Resume and tool-loop refresh preserve role order and do not perform duplicate Memory work.
7. Compaction obeys the configured turn floor, the 25-percent token target, the 20,000-token cap,
   and complete-turn/tool-group boundaries.
8. ViewManifest schemas 1, 2, and 3 remain readable; legacy manifests are not rewritten.
9. Provider overflow recovery, abort, error, and cumulative usage produce at most one outcome per
   physical attempt; multiple physical attempts may share one request sequence.
10. Tape info exposes the latest attempt cache metrics without changing existing aggregate usage
    semantics.
11. The additive trace migration preserves old rows; no renderer feature, public provider setting,
    GitHub issue, push, or pull request is created.
12. Transient retries reuse a manifest only before semantic output is committed; context recovery
    writes a new manifest, and non-chat provider modes are never transparently replayed.
13. Cache-aware v2 requests retain exactly one authoritative first-user fact across compaction,
    resume, tool-loop fitting, and context pressure, or fail before provider dispatch when its
    protected identity or budget cannot be preserved.

## Constraints

- The implementation targets the provider packages pinned in the current lockfile.
- Existing provider reasoning, tool, attachment, and tracing behavior must remain unchanged unless
  a cache transport requires preserving provider metadata that was previously discarded.
- New Tape data is append-only and uses existing entry storage.

## Non-Goals

- Guaranteeing a cache hit or a fixed cache-hit percentage.
- Adding cache controls to the settings UI.
- Persisting a second retained-history representation.
- Replacing Tape with Bub or Pi storage semantics.
- Enabling OpenAI model-specific explicit breakpoints or cache TTL controls.
- Redesigning aggregate Usage Dashboard calculations.

## Open Questions

None. The implementation decisions required for this architecture are recorded here and in the
accompanying plan.

---

### Architecture Chat Display Model Boundary Spec (`docs/architecture/chat-display-model-boundary/spec.md`)

# Chat Display Model Boundary

## Ownership

`src/renderer/src/features/chat-page/model/displayMessage.ts` owns `DisplayMessage`, its block and
usage contracts, `MessageListItem`, assistant-block renderability, and compaction classification.
The chat-page feature owns record conversion and display ordering; components, stores, and chat
composables consume this model directly.

The model is a pure feature contract. It may depend on shared types and pure values, but not on Vue,
Pinia, IPC, components, composables, or its consumers. Chat components may read this feature model
without importing the feature's page or runtime state.

## Invariants

- `components/chat/messageListItems.ts` remains absent, including compatibility re-exports.
- There is one definition of each display type and renderability policy.
- Message filtering, compaction classification, and block visibility use the same model contract
  across the list, message blocks, store, and search projection.
- This boundary does not change `MessageList` or `MessageListRow` props/events, persisted records,
  IPC payloads, streaming semantics, virtualization, or Markdown rendering.

## Regression Protection

Model and message-list consumers must resolve imports through the feature model. Renderer type
checking and the relevant display, list/window, and message-block tests protect its consumers and
observable behavior. Private file-location aliases are not a second supported API.

---

### Architecture Chat Markstream Rendering Pipeline Spec (`docs/architecture/chat-markstream-rendering-pipeline/spec.md`)

# Chat and Markstream Rendering Pipeline

## Ownership

DeepChat pins `markstream-vue@2.0.6` and uses its built-in enhanced code-block path through
`stream-diffs`. `markdownWorkerLifecycle` lazily creates the `@pierre/diffs` worker pool and injects it
with `setStreamDiffsWorkerPool` so highlighting can execute outside the renderer main thread.

```text
provider events
  -> main accumulator
  -> renderer snapshot throttle (120 ms)
  -> chat.stream.updated schema and request/Session ordering gate
  -> stream store and persisted-message projection
  -> ordered display list and outer message window
  -> MessageBlockContent artifact projection
  -> MarkdownRenderer segments
  -> Markstream pacing, parse coalescing, node batching, and heavy-node deferral
  -> DOM measurement and scroll controller
```

Main owns snapshot coalescing and the 600 ms database flush schedule. Markstream owns visible text
pacing. Streaming content and the final snapshot bypass DeepChat's static-content debounce; stale
callbacks cannot overwrite final content. Non-streaming editable document/artifact surfaces retain
length-adaptive debounce.

## Rendering Segments

Short streams and streams without a safe split render as one streaming document. Longer streams may
render a committed static prefix and a live tail. The split advances at a blank-line boundary while
avoiding an open code fence. A long paragraph or fence can prevent advancement, so the tail target
is not a strict length bound. Completion resets the split and renders one final static document.

The committed prefix uses final/static flags and incremental mounting. The live tail uses
`final=false`, `codeBlockStream=true`, simple CSS typewriter behavior, and no live node window. The
same outer message row and MarkdownRenderer retain their product identity even when internal
NodeRenderer segments change. Splitting must preserve Markdown semantics, ordering, selection,
search, and capture access.

Completed content uses Markstream's node virtualization and viewport deferral. The outer message
window owns message-row geometry; Markstream owns Markdown-node work. Search, capture, and other
full-DOM consumers use `virtualizeNodes=false`, which must disable both virtual cropping and visible
node deferral for every applicable segment.

## Code, Links, and Heavy Nodes

- Ordinary fenced code uses the built-in `CodeBlockNode` path. Streaming nodes display its lightweight
  code surface; completed eligible nodes may mount the enhanced `stream-diffs` surface.
- Do not set the removed `codeRenderer` selector or force an application-level pre/enhanced remount.
  `codeBlockStream` and `final` communicate each segment's state.
- Do not override generic `code_block` through the global custom-component registry. Link/reference
  interaction uses renderer-level delegation, code previews use the public artifact-click event,
  and Mermaid remains strict.
- Instance and segment ids isolate Markstream measurement identity. Multiple text/artifact parts of
  one message cannot overwrite each other's renderer registration.
- Heavy code, Mermaid, and KaTeX work stays eligible for viewport deferral unless the consumer
  explicitly requests full DOM.
- The simple streaming cursor avoids precise-cursor layout reads on every visible text commit.

## Snapshot and Display Contracts

The stream store replaces whole snapshots through shallow state. Already-validated blocks prefill
the renderer parsed cache when the message record is updated, avoiding an immediate second JSON
parse. Stable assistant blocks and unaffected display conversions remain reusable.

Main retains JSON normalization and schema parsing in `cloneBlocksForRenderer`, including the
compatibility behavior of undefined values in extra payloads. Preload and renderer event validation
remain trust boundaries. Persisted messages retain their JSON format.

`chat.stream.updated` remains a full cumulative snapshot. Its repeated validation and cross-process
copying are potential long-output costs requiring measurement; this contract does not introduce a
delta transport or permit removing validation. The outer display list preserves `messageIds` order,
and the message window limits mounted rows independently of Markdown segmentation.

## Performance and Validation Boundaries

The main renderer throttle allows about 8.3 snapshots per second. Integration adds no extra streaming
content timer. Parse, mount, and live-node tuning belongs to the current renderer implementation;
constants are not independent evidence that a user latency budget has been met.

The [Chat Scroll Ownership](../chat-scroll-ownership/spec.md) requirements remain in force: at most
one viewport write per frame, at most 1 CSS pixel reading-anchor drift, and no added presentation
long task above its budget. Real Chromium/Electron observations are required for these claims.

Regression protection covers final-content handoff, code fallback/enhancement, strict Mermaid,
link/reference behavior, record parsing, ordering, and full-DOM consumers. Search/capture and long
single-block cases must retain their behavior under segmentation. No current full-suite pass or
physical-display performance result is implied by this specification.

---

### Architecture Chat Scroll Ownership Plan (`docs/architecture/chat-scroll-ownership/plan.md`)

# Chat Scroll Ownership Implementation Plan

## Architecture decisions

### 1. Separate page chrome from message geometry

Introduce a focused shell structure without changing visible UI:

- `ChatPage.vue`: session/message orchestration and feature wiring.
- `ChatPageShell.vue`: three-row header/viewport/composer layout.
- `ChatMessageViewport.vue`: the only scrollable chat element and MessageList host.
- `ChatComposerRegion.vue`: existing pending lane, plan/interaction layer, input, memory chip, and
  status bar composition.

The first implementation should move existing template blocks with the smallest possible prop/emit
surface. Business logic remains in ChatPage until the shell geometry is proven.

### 2. Introduce one typed scroll controller

Create `src/renderer/src/composables/chat/useChatScrollController.ts`.

Suggested public API:

```ts
type ChatScrollRequest = {
  id: number
  sessionEpoch: number
  reason: ChatScrollReason
  priority: number
  target:
    | { kind: 'bottom' }
    | { kind: 'absolute'; top: number }
    | { kind: 'message'; messageId: string; align: 'start' | 'center' | 'one-third' }
    | { kind: 'preserve-anchor'; anchor: LogicalViewportAnchor }
}

type UseChatScrollControllerOptions = {
  viewport: Readonly<ShallowRef<HTMLElement | null>>
  resolveMessageEntry: (messageId: string) => MessageLayoutEntry | undefined
  resolveMessageElement: (messageId: string) => HTMLElement | null
  getMessageOriginTop: () => number | null
  isAutoScrollEnabled: Readonly<Ref<boolean>>
}

type ChatScrollController = {
  state: Readonly<ShallowRef<ChatScrollState>>
  beginSession: (sessionId: string) => number
  request: (request: Omit<ChatScrollRequest, 'id'>) => number
  cancel: (requestId: number) => void
  notifyUserGestureStart: (kind: 'wheel' | 'touch' | 'pointer' | 'keyboard') => void
  notifyUserGestureEnd: () => void
  notifyViewportScroll: () => void
  notifyViewportResize: () => void
  notifyLayoutBatch: (changes: MessageMeasurementChange[]) => void
  dispose: () => void
}
```

The controller owns the only low-level functions allowed to assign viewport `scrollTop`.

An exclusive operation arbiter sits in front of the frame queue. It holds at most one active
operation until completion or cancellation. Same-reason updates coalesce; higher-priority explicit
requests replace atomically; lower-priority passive work is dropped rather than replayed later.

### 3. Replace timing attribution with request attribution

Remove `programmaticScrollUntil`, `userScrollInputUntil`, and ownership decisions based on elapsed
milliseconds.

For each committed request, record:

```ts
type CommittedScroll = {
  requestId: number
  reason: ChatScrollReason
  expectedTop: number
  committedAtFrame: number
}
```

The next viewport scroll is compared with `expectedTop` using a one-pixel tolerance. Matching events
complete the request. Non-matching changes are classified as user/native geometry events. Idle
timers remain allowed only for deferring measurement/hydration work.

### 4. Make the state machine pure

Put transition logic in a small pure module, for example
`src/renderer/src/composables/chat/chatScrollState.ts`.

Inputs include session start, gesture start/end, bottom proximity, explicit navigation, submit,
stream update, history start/end, and request completion. Outputs are state plus optional scroll
intent. Unit tests cover every transition without DOM.

Key rule: once `userOwned` becomes true, passive restore, stream, resize, and measurement events
cannot clear it.

### 5. Integrate current bounded layout model

Keep `useMessageWindow` as the logical source of message positions. Narrow its responsibility to:

- estimates and measured height cache;
- stable message entries;
- total logical height;
- bounded range calculation helpers.

It must not know about scroll modes or DOM ownership. The controller receives committed measurement
changes and decides whether to follow bottom, preserve a long-list anchor, or perform no write for a
short list.

### 6. Migrate feature paths one by one

Route paths through the controller in this order:

1. Session open and restore.
2. User gesture detection and return-to-bottom.
3. Submit and streaming auto-follow.
4. Row measurement and viewport resize.
5. History pagination compensation.
6. Search navigation.
7. Spotlight/trace navigation.
8. TipTap/editor scroll containment.

After each step, prohibit the migrated direct write through focused ownership tests.

### 7. Preserve composer and plan behavior

Move composer DOM outside the viewport without unmounting existing components. Keep the same
component instances, props, emits, refs, `v-show` behavior, IME state, TipTap draft, pending queue,
memory chip, plan, and tool interaction lifecycle.

Observe composer/header size only to report viewport geometry. In reading mode, capture a logical
message anchor before applying a viewport-size change and restore it within the ResizeObserver phase
before paint. In following mode, coalesce one bottom request.

### 8. Remove viewport-level `scrollIntoView`

Search and Spotlight use the controller's message target resolution. Once the message row is
mounted, highlights are applied with `scroll: false`. Editor commands use editor-local scrolling or
an explicitly bounded scroll container so focus cannot move ChatMessageViewport.

### 9. Add staged session view preparation

Replace clear-then-restore presentation with a renderer-only prepare/commit flow:

```ts
type PreparedChatSessionView = {
  sessionId: string
  sessionEpoch: number
  messageRevision: number
  messageIds: string[]
  messageCache: Map<string, ChatMessageRecord>
  cursor: string | null
  hasMoreHistory: boolean
  layoutSnapshot?: MessageLayoutSnapshot
  viewportAnchor?: LogicalViewportAnchor
}
```

Preparation may read through existing session client/store APIs, but it must not mutate the visible
session incrementally. Commit the prepared view atomically only if its epoch is current.

Keep a small renderer-only LRU cache, initially capped at five recent sessions or a measured memory
budget. Invalidate entries when their message revision is stale. Cache entries contain view models
and layout measurements only; they do not duplicate persisted ownership or change database data.

Initial load and uncached switching run message preparation, pending-input loading, and secondary
state loading in parallel. Message preparation alone gates first message paint. Secondary results
attach afterward only if their epoch remains current.

### 10. Use progressive rendering without reducing loaded history

Continue loading the compatibility history window used by pagination and search. Render only the
latest viewport and overscan on the critical frame. Defer adjacent row mounting, non-visible heavy
Markdown hydration, and measurement refinement until browser idle or viewport approach.

The composer and shell stay mounted through session switches. An uncached target displays a stable
overlay inside ChatMessageViewport rather than collapsing the page or showing old-session content
under a new header.

## Event flow

```text
wheel/touch/pointer/key
  -> controller userOwned=true
  -> cancel restore/follow/passive requests
  -> native scroll updates viewport metrics only

stream/message/layout update
  -> feature emits typed request or layout notification
  -> controller state machine evaluates ownership
  -> request queue coalesces by priority and session epoch
  -> one rAF commit
  -> one viewport scroll write
  -> matching scroll event completes request

search/Spotlight action
  -> explicit navigation request
  -> resolve logical message top
  -> mount bounded row if necessary
  -> one viewport commit
  -> highlight without scrollIntoView
```

## Compatibility and migration

### Data compatibility

No migration is required. The refactor remains entirely in renderer layout and ephemeral state.
No store, presenter, preload, database, import/export, encryption, or persisted setting contract is
changed.

### Source migration

Use reviewable phases rather than a single rewrite:

1. Add browser regression harness and scroll-write instrumentation against current behavior.
2. Add controller/state machine while preserving current DOM.
3. Add staged session preparation and atomic commit with performance marks.
4. Route direct writes through the controller.
5. Introduce the isolated shell/viewport geometry.
6. Add bounded recent-session caching and progressive heavy-row hydration.
7. Migrate search, Spotlight, history, measurement, and editor paths.
8. Delete legacy timers, duplicated state, and direct-write helpers.

Each phase must keep the app buildable and the existing unit suite green. Do not maintain two
production scroll implementations longer than the migration requires.

### Rollback

Because no data contracts change, any phase can be reverted at source level. During migration,
controller integration should be isolated by commits so a failed phase can be reverted without
reverting unrelated ChatPage functionality.

## Performance plan

- Use `shallowRef` for controller state snapshots and DOM refs.
- Use one rAF queue for all viewport writes.
- Use one rAF measurement batch for mounted rows.
- Keep token-level stream data out of stable historical message conversion.
- Preserve bounded rendering and overscan.
- Do not toggle page-wide effects or content-visibility during scroll.
- Avoid layout read/write alternation: collect reads, compute, then commit writes.
- Cache message ID to layout entry lookup instead of repeated full-array searches on hot paths.
- Instrument request count, committed write count, anchor drift, mounted row count, and long tasks in
  development builds.
- Add performance marks for session selection, message preparation, atomic commit, first meaningful
  message paint, input readiness, and secondary state completion.
- Bound recent-session caching by count and approximate view/layout memory, with observable LRU
  eviction that never touches persisted stores.

## Test strategy

### Pure unit tests

- State transitions and priority arbitration.
- Session epoch rejection.
- User ownership durability.
- Request coalescing and cancellation.
- Exclusive operation ownership across frames and atomic replacement.
- Bottom proximity and return-to-bottom behavior.

### Vue component tests

- Shell regions remain mounted and preserve props/emits.
- Only ChatMessageViewport has chat overflow scrolling.
- Short lists perform no measurement-driven writes.
- Long-list layout batches preserve one logical anchor.
- Existing ChatPage feature tests remain black-box compatible.

### Real Chromium integration tests

The current jsdom suite cannot validate this bug class. Add a Playwright/Electron or Vitest Browser
suite that records `scrollTop`, `scrollHeight`, `clientHeight`, anchor rect, and committed request
reason on every animation frame.

Required scenarios:

1. Four-message conversation with delayed Markdown/font/layout settling.
2. Composer growth, IME input, pending lane, plan, interaction, and status changes while reading.
3. Session restore with user wheel before and after data completion.
4. Streaming with auto-scroll enabled, then user interruption.
5. Streaming with auto-scroll disabled.
6. Threshold boundaries at 159, 160, and 161 messages.
7. Long conversation with rapid wheel scrolling and row measurements.
8. Two consecutive history pages with anchor preservation.
9. Cmd/Ctrl+F visible and off-window navigation while manually scrolling.
10. Spotlight navigation during session switch.
11. Image/tool/artifact expansion above the viewport.
12. TipTap focus, selection, newline, and attachment actions.
13. Initial application load with cold and warm renderer caches.
14. Rapid A -> B -> C session switching with B resolving last.
15. Cached return to a recent session and invalidation after message revision changes.

Assertions include zero unauthorized writes, no anchor drift above one pixel, no intermediate flash,
one write per frame maximum, no stale-session request commit, no mixed-session frame, bounded heavy
DOM rows, and recorded first-paint/session-switch latency.

## Quality gates

- Focused pure/controller tests.
- Existing ChatPage and message-window suites.
- New real Chromium scroll suite.
- Full renderer suite.
- `pnpm run format`
- `pnpm run i18n`
- `pnpm run lint`
- `pnpm run typecheck:web`
- `pnpm run build`

## Risks and mitigations

| Risk | Mitigation |
| --- | --- |
| Composer extraction breaks TipTap/IME state | Move DOM without changing mount lifetime; add focus and draft tests |
| Search/Spotlight loses off-window targets | Preserve logical message addressing and mount-before-highlight flow |
| Long-list regressions | Keep `useMessageWindow`; migrate ownership, not the data model |
| Auto-follow feels delayed | One rAF coalescing adds at most one frame and removes duplicate writes |
| Viewport resize still clamps near bottom | Controller handles resize by mode: preserve anchor or follow bottom before paint |
| Migration creates two conflicting owners | Focused ownership tests and phase-by-phase removal of direct writes |
| Browser suite is slow | Keep a small critical Chromium suite separate from fast jsdom tests |
| Recent-session cache increases memory | Use a small LRU plus memory budget and observable eviction |
| Atomic preparation delays first paint | Gate on messages only; attach pending/plan/metadata later |
| Cached session view becomes stale | Key by message revision and invalidate on session/message events |

---

### Architecture Chat Scroll Ownership Spec (`docs/architecture/chat-scroll-ownership/spec.md`)

# Chat Scroll Ownership Architecture

## Status

Implementation in progress as of 2026-07-15. The exclusive controller, isolated message viewport,
request attribution, geometry observer, atomic message-view commit, and bounded renderer cache are
implemented. The real Electron scenario is present but still requires an opt-in provider run for
final browser evidence and performance numbers.

GitHub issue sync was not requested and is not part of this work.

## Implemented foundation

- `useChatScrollController` is the only low-level message viewport writer. It enforces one active
  operation and at most one physical write before the next animation-frame boundary.
- Passive owners are mode-gated: restore/follow cannot run in reading mode, while measurement
  anchoring cannot run in following mode.
- Programmatic scroll events are matched by request ID and expected position rather than timeout
  windows. User gestures cancel active and pending work atomically.
- Header, message viewport, and composer use isolated shell rows. One geometry observer reports
  viewport/message-root changes to the controller; the former eight-frame restore loop is removed.
- Message session loading keeps active selection separate from the committed message view, commits
  prepared records atomically, and uses a renderer-only five-session/count-and-memory-bounded LRU.
- Pending inputs load as secondary state and no longer block first message commit. Recent parsed
  messages and message measurements remain bounded and reusable across cached switches.
- The message store is the single readiness owner. Load, history, cache, submit, stream, pending
  input, and session hydration commits use request generations so session-ID ABA cycles cannot
  revive stale work.
- Same-session loads are fenced by live mutation revisions. Streams remain session-scoped before
  commit and are ordered and settled by request identity; stale terminals cannot clear newer
  streams and duplicate terminals cannot start duplicate refreshes.
- Unit, component, focused ownership, and opt-in Electron smoke coverage protect the new contract.

## Validation record

- Review-hardening scroll, page, keyed-parent, cache, and architecture suites passed 120/120 tests.
- Full renderer suite with four workers and a 30-second per-test budget: 168 files and 1267 tests
  passed.
- Format, i18n, lint, Node/Web typecheck, and production build passed.
- Playwright successfully discovers the opt-in real Electron scroll scenario at
  `test/e2e/specs/31-chat-scroll-ownership.smoke.spec.ts`.
- The real Electron/provider scenario and manual macOS trackpad matrix remain outstanding; no
  claim of final physical-device validation is made yet.

## User need

Chat scrolling must feel native and remain under the user's control in short, long, restored, and
streaming conversations. Opening search, loading history, rendering Markdown, resizing the composer,
showing plans, or receiving stream updates must not flash, jump backward, or steal the scrollbar.

The redesign must preserve every current ChatPage capability and all existing user data while making
the implementation smaller, testable, and maintainable.

## Problem statement

`ChatPage.vue` currently combines page orchestration, message conversion, session restoration,
history pagination, bounded rendering, row measurement, auto-follow, search, Spotlight navigation,
plan layout, composer behavior, and direct DOM scrolling in one component of more than 3,000 lines.

The same outer element is both the page shell and message scroll container. Its scroll geometry
therefore includes the top bar, search chrome, messages, plan padding, pending lanes, status content,
and sticky composer. Those regions can change height independently of messages. Chromium may clamp
`scrollTop` when the resulting maximum changes, even when JavaScript did not explicitly request a
scroll.

At the same time, several independent code paths can write scroll position through `scrollTop` or
`scrollIntoView`. Ownership is inferred through overlapping millisecond windows rather than an
explicit transaction model. Unit tests can validate numerical writes but cannot reproduce real
Chromium layout, sticky positioning, native scroll anchoring, compositor behavior, or scroll
clamping.

## Goals

1. Make the message viewport the only element that owns chat scrolling.
2. Route every programmatic scroll through one typed controller with explicit reason and priority.
3. Give an active user gesture durable ownership until the user explicitly returns to the bottom or
   requests a navigation action.
4. Preserve current short-chat, long-chat, streaming, history, search, Spotlight, capture, plan,
   interaction, pending-input, read-only, and session-switch behavior.
5. Keep the bounded message window and logical message addressing for long-chat performance.
6. Preserve all stored sessions, messages, cursors, settings, drafts, plans, and pending inputs
   without migration.
7. Reduce ChatPage responsibilities into focused components and composables with black-box tests.
8. Add real Chromium coverage for layout-dependent scroll behavior.
9. Make first chat paint and session switching fast without trading away loaded history, search
   counts, or data correctness.

## Non-goals

- No database, IPC, presenter, session schema, or message schema changes.
- No redesign of message visuals, composer visuals, search UI, plan UI, or interaction UI.
- No replacement of the existing message store or provider/streaming pipeline.
- No third-party virtual-list dependency as the source of message identity or position.
- No change to the meaning or default of `autoScrollEnabled`.
- No GitHub issue or PR creation without separate approval.

## Required architecture

### Page geometry

The page shell must use three independent layout regions:

```text
ChatPageShell (overflow: hidden)
├── ChatHeaderRegion            fixed layout row; never scrolls with messages
├── ChatMessageViewport         minmax(0, 1fr); the only overflow-y:auto owner
│   ├── history status overlay  out of normal message flow
│   ├── search overlay          out of normal message flow
│   └── MessageList             messages and virtual spacers only
└── ChatComposerRegion          fixed layout row; outside message scroll geometry
    ├── pending input lane
    ├── plan / interaction layer
    ├── ChatInputBox
    └── ChatStatusBar
```

The shell uses `display: grid`, `grid-template-rows: auto minmax(0, 1fr) auto`, and
`overflow: hidden`. Only `ChatMessageViewport` may expose `overflow-y: auto`.

Header, composer, plan, status, pending-input, and search height changes must not add or remove
content inside the message scroll extent. Viewport size changes are reported to the controller as
geometry changes, not mistaken for user scrolls.

### Single scroll owner

All chat scroll writes must pass through `useChatScrollController`. Components and feature
composables may request navigation but may not write `scrollTop`, call `scrollTo`, or call
`scrollIntoView` on the message viewport directly.

Allowed request reasons:

```ts
type ChatScrollReason =
  | 'session-restore'
  | 'auto-follow'
  | 'submit'
  | 'history-prepend'
  | 'measurement-anchor'
  | 'history-navigation'
  | 'search-navigation'
  | 'spotlight-navigation'
  | 'indicator-navigation'
  | 'user-return-to-bottom'
```

`indicator-navigation` is the "scroll to latest" indicator jumping to a specific message below the
viewport. Like search and Spotlight navigation it is an explicit user navigation: it starts
`navigating`, preserves the user's ownership across the jump, and records
`hasExplicitNavigation`.

Every request carries the active session epoch. Stale requests from an old session are discarded.
The controller commits at most one physical scroll write per animation frame.

Only one scroll operation may be active at any time, including across multiple frames. A request
cannot run concurrently with restore, follow, history, measurement, search, or Spotlight work.
Repeated requests from the same owner coalesce into the active operation. A higher-priority explicit
request replaces the active operation atomically. Lower-priority passive requests are discarded,
not queued for a delayed write that could pull the viewport back later. A user gesture cancels the
active operation and all uncommitted work in one step.

### Ownership and priority

Priority is explicit and stable:

1. Active user gesture or durable reading mode.
2. Explicit user navigation: search, Spotlight, return-to-bottom.
3. History-prepend anchor preservation.
4. Session restore before any user interruption.
5. Generation auto-follow when enabled and still owned.
6. Passive measurement refinement.

User ownership is event-driven, not timeout-driven. Wheel, touch, scrollbar pointer, and scroll
keys enter reading mode. Reading mode persists until one of these occurs:

- the user scrolls back within the bottom threshold;
- the user presses the explicit return-to-bottom action;
- the user starts an explicit search or Spotlight navigation;
- the session changes and a new session epoch begins.

Idle timers may be used to reduce measurement and rendering work, but they must never decide who
owns the scrollbar.

### Controller state

```ts
type ChatScrollMode =
  | 'restoring'
  | 'following'
  | 'reading'
  | 'navigating'
  | 'history-preserving'

type ChatScrollState = {
  sessionEpoch: number
  mode: ChatScrollMode
  userOwned: boolean
  nearBottom: boolean
  activeGesture: boolean
  lastCommittedRequestId: number
  /** Whether ownership must be restored to the user when an explicit navigation completes. */
  resumeUserOwnedAfterNavigation: boolean
  /** True once the user explicitly navigated (search, Spotlight, indicator) in this session. */
  hasExplicitNavigation: boolean
}
```

Programmatic writes use request IDs and expected targets. The following `scroll` event is matched to
the committed request instead of being classified by a time window. Unexpected position changes are
treated as native layout/clamping events and observed separately.

### Short and long conversations

- Below the existing windowing threshold, all messages remain rendered and row measurements never
  write scroll position.
- Above the threshold, the existing logical layout model continues to provide message top/bottom
  positions and bounded DOM rendering.
- Measurement batches update the logical height map once per frame.
- When reading a long conversation, one stable logical message anchor is preserved in the same
  frame as the height-map commit.
- Overscan and mounted-row bounds remain compatible with the current long-chat contract.

### Session restore

- A session begins with a new epoch and one initial bottom request.
- Late restore, resize, or message responses from an older epoch are ignored.
- The restore request is permanently cancelled by any user gesture in that epoch.
- Repeated eight-frame or broad ResizeObserver bottom writes are removed. If asynchronous content
  requires settling, the controller follows only while mode remains `restoring` or `following` and
  coalesces to one frame write.
- Resize-driven following obeys `autoScrollEnabled`; restore positioning remains allowed regardless
  of that preference, but later message-root or composer geometry changes may not bypass it.
- A layout-induced native scroll may update bottom proximity, but it may not surrender durable user
  ownership without an active user gesture.

### First load and session switching

Loading is split into critical and secondary paths:

```text
critical path
  session selection
    -> prepare latest message window
    -> atomically commit session view
    -> render latest bounded rows
    -> position once
    -> input and message viewport interactive

secondary path
  pending inputs + plans + metadata + adjacent pre-measurement + optional pre-hydration
```

- Do not clear the visible message view before the target session window is ready to commit.
- Never expose a mixed state containing the new session header and old session messages.
- Uncached switches use a fixed-geometry loading overlay inside the isolated viewport. Cached
  switches restore a renderer-only recent-session view immediately.
- Keep a bounded in-memory LRU cache of recent session view models, logical height maps, and last
  viewport anchors. The cache is ephemeral, keyed by session ID plus message revision, and never
  persisted.
- A prepared session result commits only when its session epoch is still current.
- Selection changes advance both message-load and history generations, including A-B-A cycles;
  cache misses do not advance either generation.
- A failed or superseded preparation does not expose the target view or enable its composer unless a
  matching cached view was already committed safely.
- Message data and the latest viewport are the critical path. Pending inputs, plan state, status
  metadata, and optional adjacent-row hydration load in parallel but do not block first message
  paint.
- Load the existing history count for compatibility, pagination, and search correctness, but mount
  only the bounded viewport plus overscan. Loaded records must not imply mounted heavy DOM rows.
- Preserve the page shell, composer instances, TipTap state, and global layout across session
  switches; replace only session-scoped view data.

### Streaming and submit behavior

- Submitting a new message explicitly transfers ownership to bottom-follow for that new turn.
- Streaming follows the bottom only when `autoScrollEnabled` is true and the user has not entered
  reading mode afterward.
- Token revisions do not independently write scroll position. They invalidate one coalesced
  auto-follow request.
- Stream snapshots are monotonic within a request. Each request settles once, terminal events are
  ignored when a newer same-session request owns the view, and post-terminal snapshots cannot
  resurrect streaming state.
- Async submit, queue, steer, and compaction continuations must still own the same page generation
  before mutating composer or optimistic view state.
- When the user scrolls away, streaming continues without changing the viewport.
- Coalesced search or Spotlight requests preserve the ownership state captured by the first active
  navigation transaction until that transaction completes.

### History pagination

- Pagination requires a full initial history window, a scrollable viewport, an upward user intent,
  and a threshold crossing owned by the controller.
- Upward intent that begins while already inside the top threshold arms pagination even when the
  browser emits no additional `scroll` event.
- Only one history request may exist per session epoch.
- The pre-request logical anchor and current user offset are captured.
- After prepend, the controller commits one anchor-preserving correction before paint.
- Loading chrome is overlay-only and does not change message origin or scroll extent.
- Touch ownership and frozen window measurements remain active through native inertial scrolling,
  ending on `scrollend` with an idle fallback.

### Search, Spotlight, and editor containment

- Search and Spotlight resolve a `messageId` through the logical layout model.
- Off-window navigation first requests the correct logical viewport, waits for the target row to
  mount, then applies highlight without a second viewport-level `scrollIntoView`.
- Same-query highlight refresh never navigates.
- TipTap selection scrolling is contained inside the editor. It must not scroll the message
  viewport or page shell.
- Cmd/Ctrl+F result counts and navigation semantics remain unchanged.

## Functional compatibility matrix

| Capability | Required result |
| --- | --- |
| Session open/switch | Latest messages appear at bottom unless the user interrupts restore |
| First application chat load | Shell and input become interactive before secondary session state |
| Cached session switch | Recent session view and anchor restore without an empty-state flash |
| Uncached session switch | Fixed viewport loading state, then one atomic target-session commit |
| Auto-scroll enabled | New generation follows bottom until user scrolls away |
| Auto-scroll disabled | Generation never steals the viewport |
| New message submit | New turn moves to bottom once, then respects later user ownership |
| Short conversations | No pagination, measurement correction, flash, or rollback |
| Long conversations | Bounded mounted rows, stable spacers, no blank gaps |
| Older-history loading | Same message and intra-message offset remain visible |
| Cmd/Ctrl+F | Full loaded-message count; next/previous works across virtual windows |
| Spotlight/trace jump | Target mounts, centers once, highlights, and clears pending navigation |
| Streaming Markdown | No full-list rebuild and no viewport movement in reading mode |
| Images/artifacts/tools | Late size changes preserve long-list anchors without short-list writes |
| Plan/interaction/pending lane | Visual behavior unchanged; no participation in message scroll extent |
| Composer resize/focus/IME | Draft and focus remain intact; message viewport is not scrolled |
| Read-only/subagent sessions | Existing display and navigation behavior remains unchanged |
| Capture/export | Full loaded-message capture behavior remains available |

## Data and compatibility guarantees

- No changes under `src/main`, `src/preload`, database schemas, encryption, import/export, or
  persisted configuration.
- No changes to message IDs, `renderKey`, ordering, pagination cursor, session identity, or message
  cache semantics.
- `autoScrollEnabled` retains its current persisted key and meaning.
- Scroll controller state is renderer-only and ephemeral. It is reset on session epoch changes and
  is never persisted.
- Existing user data requires no migration and remains readable by both pre-refactor and
  post-refactor builds.
- Rollback is a source-code rollback only; it does not require data rollback.

## Performance and experience acceptance criteria

1. During an active user gesture, no unauthorized programmatic scroll write occurs.
2. In reading mode, the selected logical anchor drifts by at most 1 CSS pixel across Markdown,
   image, tool, plan, composer, and streaming layout changes.
3. Short conversations with 1–20 messages perform zero measurement-driven scroll writes.
4. One user navigation action produces at most one viewport write plus one highlight-only DOM pass.
5. One animation frame contains at most one committed viewport scroll write.
6. Across frames, at most one scroll transaction remains active; no second owner can alternate
   writes with it.
7. Long conversations keep mounted heavy message rows bounded by the active window and overscan.
8. Continuous wheel/trackpad scrolling has no application-generated long task above 50ms and no
   repeated frame-by-frame full-list conversion.
9. Streaming scroll work is coalesced to animation frames; token frequency does not equal scroll
   write frequency.
10. Search, history, and Spotlight navigation complete without visible intermediate window swaps.
11. Existing renderer unit suites, focused ownership tests, typecheck, lint, and i18n pass.
12. Chromium integration scenarios show no flash, rollback, native clamp, or scrollbar theft.
13. On the agreed reference machine, warm cached session switches reach first meaningful message
    paint at p95 within 100ms, uncached local switches within 250ms, and initial chat shell
    interaction within 150ms. CI records these metrics and uses a wider non-regression budget where
    machine variance makes absolute limits unsuitable.
14. Session switching performs no full heavy-DOM mount for every loaded message and produces no
    intermediate empty or mixed-session frame.

## Maintainability acceptance criteria

- `ChatPage.vue` becomes an orchestration shell rather than a scroll implementation owner.
- Direct message viewport writes are restricted to the controller module by focused ownership tests.
- Scroll state transitions are pure and unit-testable.
- Feature composables request typed scroll intents instead of sharing mutable timers.
- Each asynchronous operation is scoped by session epoch and cancellable cleanup.
- Comments explain invariants and ownership, not timing folklore.

## Linked specifications

- `docs/issues/chat-history-search-scroll-coordinates/spec.md`
- `docs/ARCHITECTURE.md` Desktop platform contract

---

### Architecture Chat Scroll Ownership Tasks (`docs/architecture/chat-scroll-ownership/tasks.md`)

# Chat Scroll Ownership Tasks

## Phase 0: Baseline and browser evidence

- [ ] Add development-only scroll instrumentation with reason, request ID, session epoch, expected
  top, actual top, scroll height, client height, and anchor ID.
- [x] Add a real Chromium test harness capable of exercising ChatPage layout and native scrolling.
- [ ] Reproduce and record the four-message rollback before changing architecture.
- [ ] Add baseline scenarios for restore, streaming, composer resize, plans, search, Spotlight,
  history, editor focus, and long-window measurement.

## Phase 1: Pure ownership model

- [x] Add typed scroll reasons, targets, requests, state, and session epochs.
- [x] Implement the pure scroll state machine.
- [x] Test priority, durable user ownership, explicit navigation, restore cancellation, auto-follow,
  and stale epoch rejection.
- [x] Implement a one-write-per-frame request queue.
- [x] Implement an exclusive operation arbiter with one active owner across frames.
- [x] Test that passive restore/follow/measurement operations cannot alternate viewport writes.

## Phase 2: Session preparation and performance baseline

- [x] Add performance marks for selection, preparation, commit, first message paint, input
  readiness, and secondary state completion.
- [x] Add an immutable prepared-session-view type with stale-epoch rejection.
- [x] Replace visible clear-then-load behavior with one atomic target-session commit.
- [x] Allow message readiness to unblock paint before pending inputs, plans, and metadata.
- [x] Add a bounded renderer-only LRU for recent session views, measurements, and anchors.
- [x] Add cache revision invalidation and memory/count eviction tests.
- [x] Add cold initial load, cached switch, uncached switch, and rapid A/B/C race tests.

## Phase 3: Controller integration on current DOM

- [x] Add `useChatScrollController` around the current viewport.
- [x] Route session restore through the controller.
- [x] Route submit and streaming auto-follow through the controller.
- [x] Route history compensation and measurement anchoring through the controller.
- [x] Route search and Spotlight navigation through the controller.
- [x] Match programmatic scroll events by request ID/expected target instead of time windows.
- [x] Add a focused ownership test preventing new direct viewport writes outside the controller.

## Phase 4: Isolated page geometry

- [ ] Add `ChatPageShell.vue` with header, viewport, and composer regions.
- [ ] Add `ChatMessageViewport.vue` as the only chat overflow owner.
- [ ] Add `ChatComposerRegion.vue` without changing child mount lifetimes or public behavior.
- [x] Move search and history status to overlays outside message flow.
- [x] Remove top bar, plan padding, pending lane, status, and composer from message scroll extent.
- [x] Preserve viewport anchors across header/composer ResizeObserver changes by controller mode.

## Phase 5: Feature compatibility

- [ ] Preserve session open/switch bottom behavior and user interruption.
- [ ] Preserve `autoScrollEnabled` semantics.
- [ ] Preserve pending assistant and generating placeholder transitions.
- [ ] Preserve rate-limit, plan, tool interaction, memory, queue, and read-only behavior.
- [ ] Preserve two-page history loading and current cursor/store behavior.
- [ ] Preserve full loaded-message Cmd/Ctrl+F counts and navigation.
- [ ] Preserve Spotlight/trace navigation and highlighting.
- [ ] Contain TipTap scrolling inside the editor/composer region.
- [ ] Preserve capture/export access to all loaded messages.
- [ ] Preserve loaded history/search counts while progressively mounting only bounded heavy rows.
- [ ] Preserve shell, composer, TipTap, and input readiness across session switches.

## Phase 6: Performance and cleanup

- [x] Keep short lists fully rendered with zero measurement scroll writes.
- [x] Keep long-list mounted rows bounded and optimize message entry lookup.
- [x] Batch layout reads, measurements, and one viewport write per frame.
- [x] Remove legacy scroll timers, duplicated booleans, direct-write helpers, and stale comments.
- [ ] Reduce ChatPage to orchestration and extract focused feature composables where ownership is
  clear.
- [ ] Verify no scroll-state CSS mutation causes large repaint or compositor churn.
- [ ] Meet initial shell, cached-switch, and uncached-switch latency budgets on the reference
  machine.
- [ ] Verify recent-session cache memory stays within its configured budget.

## Phase 7: Validation and handoff

- [x] Pass all pure state/controller tests.
- [x] Pass ChatPage and message-window component tests.
- [ ] Pass the real Chromium scroll matrix with anchor drift at or below one pixel.
- [x] Pass the full renderer suite.
- [x] Run format, i18n, lint, Web typecheck, and build.
- [ ] Manually verify trackpad and mouse-wheel behavior on macOS in short and long conversations.
- [ ] Record before/after performance and scroll-write metrics.
- [ ] Record cold first-load, warm cached-switch, uncached-switch, and rapid-switch race metrics.
- [ ] Update this specification and the retained scroll-coordinate issue with final implementation results.

## Phase 8: Review hardening

- [x] Make request queue epoch ordering monotonic and preserve newer pending work on stale takes.
- [x] Expire the immediate-write frame guard at the next frame boundary.
- [x] Preserve user ownership across native layout scrolls and coalesced explicit navigation.
- [x] Gate resize-driven auto-follow on `autoScrollEnabled` without blocking initial restore.
- [x] Keep failed or superseded session preparation behind the safe committed-view boundary.
- [x] Keep touch ownership active through inertial scrolling and arm top pagination without relying
  on a follow-up `scroll` event.
- [x] Persist recent measurement snapshots across the keyed `ChatPage` remount lifecycle.
- [x] Replace timing-only Electron search assertions with observable completion state.
- [x] Add regressions for every review-hardening invariant and rerun the full quality gates.
- [x] Make committed message readiness store-owned and fence same-session refreshes against live
  message mutations.
- [x] Fence stream terminal identity, pending-input writes, history overlap, submit continuations,
  and A-B-A session hydration.

---

### Architecture Chatpage Decomposition Plan (`docs/architecture/chatpage-decomposition/plan.md`)

# Plan: ChatPage 分解与竞态治理

> 以下为当时的实施计划。PR #2384 已移除计划清单能力及 `usePlanFloatLifecycle`；
> 当前边界以 [spec.md](./spec.md) 和 [能力退出说明](../agent-plan-retirement/spec.md) 为准。

## 实施顺序

按"每抽一个 composable 即 typecheck + 测试"的节奏,从耦合最少的关注点开始,
逐步收编模块级令牌;滚动仲裁族(`useChatScrollController` 等)保持不动,只归拢胶水调用。

1. **死代码清理** — 删除零引用的 `composables/message/useMessageScroll.ts`(339 行,
   旧 vue-virtual-scroller 实现)及其孤儿测试、`ScrollInfo` 类型。
2. **useDisplayMessages + usePlanFloatLifecycle** — 记录→DisplayMessage 转换、
   稳定/流式分离、占位符四态机(见 spec 决策记录)、Plan 快照生命周期。
3. **useChatSearch** — 会话内搜索,包装 `lib/chatSearch` 的 rAF/highlight 调度。
4. **useListGestures + useMessageVirtualization** — 手势原子与虚拟化原子,
   再组合接入主文件;composable 声明顺序移到会话 watch 之前。
5. **useComposerSubmit** — 发送/排队/steer/命令/compaction 统一提交路径,
   自持 `attachmentFilterToken`;四个提交入口的重复守卫收敛为 `canSubmitNow()`。
6. **useSessionRestore** — 会话恢复 epoch(`restoreRequestId`)、`canWriteSessionView`
   写 gate、启动延迟恢复调度、`deactivate()` 卸载语义;主文件通过
   `currentRestoreRequestId()` 读取最新令牌。

## 验证策略

- 每步:`pnpm run typecheck:web` + `ChatPage.test.ts`(82 用例)。
- 收尾:`pnpm run format` / `lint` / `i18n` / `test:renderer` 全量,
  与 origin/dev 对照隔离既有失败。
- 行为零漂移由测试 + 逐行 diff 审查共同保证;竞态令牌语义(闭包内最新值 vs 快照)
  单独走一轮对抗性 review。

## 回滚

每个 composable 一个独立 commit。因抽取提交之间存在依赖(后一个 composable 的接入
依赖前面已建立的装配结构与导入),回滚须按提交的**逆序**进行,或连同依赖它的后续
提交一并回滚,不能孤立 revert 中间某一步。滚动仲裁契约(`useChatScrollController`
及其协作模块)全程未动,整体回滚到基线不影响该子系统。

---

### Architecture Chatpage Decomposition Spec (`docs/architecture/chatpage-decomposition/spec.md`)

# ChatPage 分解与竞态治理

## 背景

ChatPage 原位于 `src/renderer/src/pages/ChatPage.vue`，膨胀到 3050 行；现已迁至
`src/renderer/src/features/chat-page/ChatPage.vue`。单个 `<script setup>` 承载 10+ 个关注点：
会话恢复、消息记录转换、虚拟窗口、测量批处理、手势/滚动、占位符状态机、
会话内搜索、语音输入、发送/排队/steer、消息操作。竞态治理靠散落的手写令牌
（`sessionRestoreRequestId`、`voiceInputConfigToken`、`attachmentFilterToken`、
`chatScrollSessionEpoch`、`pendingAssistantPlaceholderSeq`）和大量模块级 `let`，理解成本极高。

计划清单能力已退出：原 `usePlanFloatLifecycle`、plan store 和浮层已移除，事件桥仅管理
window 事件；交互 dock 继续承载问题和权限请求。历史数据边界见
[agent-plan-retirement](../agent-plan-retirement/spec.md)。旧 `plan.md` / `tasks.md` 保留实施历史。

## 现状边界（已良好分层，不动）

滚动仲裁已是成熟架构，本次仅把 ChatPage 内的胶水调用归拢，不改其内部时序：

- `useChatScrollController` — 独占滚动通道、状态机驱动、rAF commit/verify。
- `chatScrollState` — 纯 reducer 状态机（mode/userOwned/nearBottom/activeGesture）。
- `chatScrollOperationArbiter` — 物理滚动条独占所有权 + 优先级抢占。
- `chatScrollRequestQueue` — 单槽优先级队列。
- `useMessageWindow` — 虚拟列表布局、测量高度、快照捕获/恢复。
- `recentMessageMeasurementCache` — 最近会话测量 LRU 缓存。

## 目标

1. 主文件收缩到 ~400 行，只做装配 + 模板。
2. 每类竞态由一个自持 epoch/token 的 composable 独立治理，边界清晰。
3. 删除确认的死代码。
4. 行为零漂移：每抽一个 composable 即 typecheck；最终 lint/test/真实应用验证。

## 死代码（确认零引用）

- `composables/message/useMessageScroll.ts`（339 行）— 全项目零引用，旧 vue-virtual-scroller 实现。
- `composables/message/types.ts` 中的 `ScrollInfo` 接口 — 仅被上文件引用（`CaptureOptions` 仍在用，保留）。

## 当前分解边界（11 个 composable）

| composable | 职责 | 收编的状态/竞态 |
|---|---|---|
| `useSessionRestore` | 会话切换、令牌 gate、恢复 | `sessionRestoreRequestId`、`canWriteSessionView`、启动延迟恢复调度 |
| `useDisplayMessages` | 记录→DisplayMessage、稳定/流式分离、缓存；**并含占位符四态机** | `displayMessageCache`、`assistantRenderKeyByMessageId`、`pendingAssistantPlaceholder` 全套 |
| `useMessageVirtualization` | 窗口范围、测量批处理、锚点补偿、几何观测 | `pendingMeasureQueue`、rAF flush |
| `useListGestures` | wheel/touch/pointer/键盘手势 → 控制器 | ~15 handler、`isListScrolling` |
| `useChatSearch` | 会话内搜索（包 `lib/chatSearch`） | 搜索 rAF、highlight 调度 |
| `useComposerSubmit` | 发送/排队/steer/命令/compaction | 统一 gate 后的提交路径、`attachmentFilterToken` |
| `useVoiceInput` | 语音输入可用性、识别与转写适配 | `voiceInputConfigToken`、模型配置订阅、speech cleanup |
| `useToolInteraction` | 待处理工具/问题交互聚合与响应 | 子 agent progress 解析、响应单飞锁、当前页面会话刷新 |
| `useMessageActions` | 消息重试、编辑、删除确认、fork、continue | 删除确认状态、消息操作刷新策略 |
| `usePendingInputActions` | 已排队输入的编辑、移动、删除、steer | 队列项附件/技能保留、steer 守卫 |
| `useChatPageEventBridge` | window 事件订阅 | 显式 `start`/`stop`，避免监听泄漏与重复订阅 |

> 决策记录：原计划独立的 `useAssistantPlaceholder` 并入 `useDisplayMessages`。占位符的
> renderKey 交接直接写入消息转换缓存读取的 `assistantRenderKeyByMessageId`，显隐判定依赖
> `hasFirstStreamingContent` / `ephemeralRateLimitBlock`，占位符行本身注入流式尾部组装；
> 强拆会造成两个 composable 双向共享三份可变状态，不如单一所有者边界清晰。

## 约束

- 每个 composable 自持一个防竞态 epoch/token，替代散落的模块级令牌。
- 不改 `useChatScrollController` 及其协作模块的对外时序契约。
- 优先 shadcn-vue / VueUse（`useEventListener`、`useRafFn` 等）替代手写 rAF/监听器。
- i18n key 不新增；用户可见字符串沿用现有 key。

## 模块位置决策

`ChatPage.vue` 与其 11 个 composable 位于 `features/chat-page/`（分别是
`features/chat-page/ChatPage.vue` 与 `features/chat-page/composables/`），而非全局 `lib/` /
`components/`。这是有意的 feature-local 布局：它们是 ChatPage 独占的私有逻辑，强耦合页面
props 与页面级 store 组合，不面向复用。`ChatTabView` 只通过
`@/features/chat-page/ChatPage.vue` 组合该 feature；页面内部只使用相对的 `./composables/*`
引用。就近放置能让读者一眼看出归属、避免误当作通用工具被其他页面引用。

同一 feature 的纯展示契约位于 `features/chat-page/model/displayMessage.ts`：它拥有
`DisplayMessage` 家族、assistant block 的 renderability policy 与 compaction 判定。chat list、
message block 组件、store 与测试可读取该纯 contract，但不得反向依赖 ChatPage、feature composable
或 store；该模块只依赖 shared types。

`lint:architecture` guard 通过（未对该布局设硬约束）。若后续有第二个页面需要复用其中某个
composable，再将其上提到 `lib/` 并补通用化改造。

---

### Architecture Chatpage Decomposition Tasks (`docs/architecture/chatpage-decomposition/tasks.md`)

# Tasks: ChatPage 分解与竞态治理

> 以下保留历史完成记录，不代表当前能力。PR #2384 已移除 plan 生命周期和更新订阅；
> 当前边界见 [spec.md](./spec.md)。

- [x] 写 `spec.md` / `plan.md` / `tasks.md`
- [x] 删除死代码 `useMessageScroll.ts`(339 行)+ 孤儿测试 + `ScrollInfo` 类型
- [x] 抽取 `useDisplayMessages`(含占位符四态机,见 spec 决策记录)
- [x] 抽取 `usePlanFloatLifecycle`
- [x] 抽取 `useChatSearch`
- [x] 抽取 `useListGestures` / `useMessageVirtualization` 原子并组合接入
- [x] composable 声明顺序移到会话 watch 之前
- [x] 抽取 `useComposerSubmit`(自持 `attachmentFilterToken`,守卫收敛 `canSubmitNow()`)
- [x] 抽取 `useSessionRestore`(自持 `restoreRequestId` epoch + `deactivate()` 卸载语义)
- [x] `useAssistantPlaceholder` 决策:并入 `useDisplayMessages`,不单独成文件(spec 有记录)
- [x] 全量验证:typecheck / lint / format / i18n / `ChatPage.test.ts` 82 用例
- [x] 对抗性 review(逐行对照基线 `8b121ede8`):发现并修复 `usePlanFloatLifecycle`
      中 linger 状态从 `ref` 降级为普通对象导致的响应性回归(完成后浮窗不再驻留 1200ms);
      其余确认零漂移
- [x] 合并 origin/dev 并复核(Settings/Provider/sidepanel 的 18 个失败为 dev 既有,与本分支无关)
- [x] 将 ChatPage 和 7 个私有 composable 迁至 `features/chat-page/` 并更新 route host、测试与
      feature 内部导入；只变更模块解析路径，保留模板与运行逻辑
- [x] 抽取 `useVoiceInput`：自持 `voiceInputConfigToken`、模型配置订阅与 speech cleanup；页面仅
      装配当前模型选择和模板状态。新增竞态、订阅释放和 speech adapter 单测。
- [x] 抽取 `useToolInteraction`：收束顶层/子 agent 待处理交互聚合、响应单飞锁和当前页面会话
      刷新；新增过滤、单飞/动态 session 与失败复位单测。
- [x] 抽取 `useMessageActions` / `usePendingInputActions`：收束消息重试、删除确认、编辑、fork、
      continue 与队列项变更/steer；保持原有 session 刷新和交互守卫。
- [x] 抽取 `useChatPageEventBridge`：通过显式 `start`/`stop` 管理 window 事件与 plan 更新订阅，
      页面保留 viewport 生命周期顺序。
- [ ] 后续(可选):主文件继续向 ~400 行收缩（viewport/session 装配另行切片）

---

### Architecture Ci Release Packaging Plan (`docs/architecture/ci-release-packaging/plan.md`)

# CI and Release Packaging Contract — Implementation Plan

> Requirements and acceptance criteria are defined in [spec.md](./spec.md), and execution progress is
> recorded in [tasks.md](./tasks.md).

## 1. Architecture

Build, Release, package regression, and the pull-request package gate are thin callers. They invoke
three operating-system reusable workflows with an immutable source SHA and architecture matrix. The
reusable workflows own native preparation and package verification, while deterministic Node
scripts own file discovery, manifests, updater metadata, size policy, impact classification, and
release assembly.

The package layer has two artifact purposes:

- `distribution` creates an exact downloadable target artifact. On macOS it requires and verifies
  signing and notarization.
- `verification` creates a package only inside the current runner, enforces smoke and size policies,
  and uploads diagnostics without distributing the unsigned installer.

Build and Release always request distribution artifacts. Pull-request package checks and scheduled
or manually dispatched package regression always request verification.

Verification deliberately retains the complete target set configured for an operating system. The
CLI can select individual electron-builder targets, but adding an updater-only PR mode would create
a second manifest and size-policy contract. Latency is reduced by classifying fewer changes and
running fewer operating systems, not by weakening an affected target's package coverage.

## 2. Shared Package Contract

`scripts/ci/package-contract.mjs` defines the six target IDs, allowed architectures, artifact roles,
raw updater metadata names, and required public release files. Other scripts import this contract
instead of maintaining extension globs independently.

`scripts/ci/package-manifest.mjs` locates exactly one file for every required role, rejects unknown or
unsafe entries, hashes files, validates raw electron-builder metadata, and stages a self-contained
target artifact. It derives check results from completed workflow evidence. For macOS distribution it
also invokes the existing application and DMG verification helpers before recording distribution
status.

The internal artifact layout is:

```text
package-output/
  manifest.json
  files/
  metadata/
  reports/
```

Only distribution-mode `package-output` directories include distributable files. Verification mode
retains reports and a manifest locally and uploads only diagnostic content.

## 3. Installer Size Policy

The Light OCR budget file retains only component budgets. Installer facts and policy move to:

- `resources/package-size-baseline.json`
- `resources/package-size-policy.json`

The baseline records bytes and SHA-256 for the selected package roles from run `29978292769`. A
baseline-import command validates that all six target directories contain exactly one expected
candidate for every measured role before writing reviewable JSON.

The comparison command matches by target and role rather than versioned filename. Initial growth and
shrink limits are both 90 MiB. It always writes a report, including on policy failure.

## 4. Reusable Workflows

Each OS reusable workflow validates its inputs before dependency installation, re-declares CI and
JavaScript-action runtime environment, uses a bounded timeout, and keeps repository permissions
read-only.

Common behavior includes frozen installation, Sharp target configuration followed by a second frozen
installation, runtime setup, source build, target package creation, packaged smoke tests, manifest
generation, and optional size comparison.

Operating-system differences remain explicit:

- Windows uses PowerShell network isolation for Light OCR and verifies both supported plugins.
- Linux uses network namespaces, verifies OpenDAL, and omits CUA for ARM64.
- macOS uses sandbox network isolation, verifies the application and DMG distribution chain, and
  receives signing secrets only for distribution.

Distribution outputs use unique `deepchat-package-<platform>-<arch>` artifact names, error on missing
files, and disable redundant artifact compression.

## 5. Callers

`build.yml` retains the platform dispatch selector and invokes the appropriate OS matrices with
`distribution`. macOS secrets are passed only to the macOS caller.

`package-regression.yml` supports `workflow_call`, `workflow_dispatch`, and a daily 18:37 UTC
schedule. It invokes all six targets with `verification`, enforces installer size, and passes only the
runtime token and existing non-signing build configuration. It is the full nightly/manual regression
suite and is not nested inside the fast PR workflow. Scheduled failures share one open GitHub issue,
which is updated on repeated failures and closed after recovery.

`prcheck.yml` keeps only static, main, renderer, Native Memory, source-build, and aggregate jobs for
PRs targeting `dev`. `pr-required` therefore reports as soon as fast code-quality checks complete.

`package-check.yml` is a separate, always-started workflow for PRs targeting `dev`:

1. Check out full history and validate the exact base/head commit pair.
2. Load the classifier only from the base revision. If it does not exist during the one-time
   contract bootstrap, validate both `package.json` snapshots and conservatively select all targets
   without executing candidate classifier code.
3. Classify changed paths into Windows, Linux, and macOS decisions with rule evidence.
4. Invoke both architectures for each affected operating system using complete `verification`
   packaging and the installer-size gate.
5. Run `package-required` with `always()` and require each OS job to be successful when selected or
   skipped when not selected.

The workflow intentionally has no `paths` or `paths-ignore` trigger. GitHub can leave a skipped
workflow's required status pending, whereas an always-present aggregate can represent both selected
and intentionally skipped native jobs safely.

Classifier rules are explicit and ordered:

- shared builder config, native dependency manifests, runtime installers, plugins, package smoke,
  and manifest/size tooling select all operating systems;
- package manifest comparison selects only production dependency, Electron toolchain, package
  metadata, lifecycle, build, runtime, plugin, and package-smoke changes, while the lockfile remains
  conservatively shared;
- `_package-<os>.yml`, platform signing/installer files, and platform icons select one operating
  system;
- release preflight/assembly, unrelated workflows, generated provider/ACP registries, ordinary
  application code, and documentation select none.

The classifier's own path selects all operating systems under the base version, preventing a
classifier-only candidate change from weakening its own gate. A missing base classifier also
selects all targets, so bootstrap cannot turn missing policy into a skip. Workflow orchestration
remains candidate-controlled and therefore relies on parsed contract tests and review policy.
Output keys remain backward compatible; a breaking schema change requires two PRs.

## 6. Release

A preflight job resolves and checks the tag, main ancestry, package version, CHANGELOG section, and
release notes before invoking any package matrix.

After all six distribution artifacts are downloaded, `scripts/ci/assemble-release.mjs`:

1. Validates target completeness, source identity, purpose, checks, paths, sizes, and SHA-256.
2. Recomputes and validates raw updater metadata SHA-512 and sizes.
3. Copies only the current public contract into a clean staging directory.
4. Merges Windows and macOS metadata using electron-updater architecture-selection semantics.
5. Preserves separate Linux architecture metadata.
6. Writes `release-index.json` with target evidence and SHA-256 for the other eighteen assets.
7. Verifies the final staging directory contains exactly nineteen allowed assets.

The draft release action runs only after assembly succeeds and is the only job with write
permission. Before upload, `scripts/ci/verify-release-assets.mjs` revalidates the local index and all
nineteen files. An existing draft may contain only known contract names; after upload the verifier
requires exactly nineteen GitHub assets whose API-reported sizes and SHA-256 digests match the local
contract.

Package manifests bind both workflow run ID and run attempt. A partial rerun that mixes successful
artifacts from different attempts fails closed; maintainers rerun all jobs for a release retry.

## 7. Tests and Validation

Vitest fixtures cover success and every fail-closed boundary: missing targets or roles, unsafe paths,
duplicates, digest changes, invalid updater references, wrong architecture metadata, verification
manifests in Release, missing macOS distribution evidence, and package-size limits.

Parsed-YAML workflow tests cover reusable inputs, runner mappings, permissions, explicit secret
passing, pinned actions, environment declarations, distribution versus verification behavior,
artifact upload safety, independent PR aggregates, package-impact success/skip combinations, and
release preflight ordering.

Classifier tests cover shared, platform-specific, and ignored paths, malformed input, NUL-delimited
CLI input, evidence output, and base-owned execution. Updater compatibility tests exercise the
installed electron-updater architecture selectors so dependency upgrades cannot silently invalidate
the assembled Windows, macOS, or Linux metadata conventions.

Validation proceeds through focused tests, complete main and renderer suites, type checking, the
canonical build, format, localization, lint, and a final format check. Verification-mode packaging
has passed on all six GitHub-hosted native runners. Distribution-mode Apple signing/notarization and
draft release publication remain pending until a release or manual Build run.

## 8. Rollback

The implementation is divided into documentation, deterministic tooling, reusable package workflows,
PR gate integration, and release assembly commits. The PR gate is independently reversible:
removing `package-check.yml` and restoring the previous classifier call in `prcheck.yml` does not
change Build or Release behavior. The baseline and tooling commits do not change application runtime
behavior and can remain inert during a workflow rollback.

---

### Architecture Ci Release Packaging Spec (`docs/architecture/ci-release-packaging/spec.md`)

# CI and Release Packaging Contract — Specification

> Status: **implemented; scoped PR gate awaits remote validation**
>
> Classification: **architecture**
>
> GitHub issue: **not requested**

This document defines the maintained native packaging, regression, and release-assembly contracts.
The implementation design is described in [plan.md](./plan.md), and execution progress is tracked in
[tasks.md](./tasks.md).

## 1. Purpose

The Build and Release workflows currently duplicate six native package paths, rebuild a historical
baseline once per target for installer-size checks, and collect release files through permissive
globs. A missing target or updater payload can therefore be hidden by tolerant copy commands, while
routine packaging changes require parallel edits in multiple workflow files.

The new architecture makes three operating-system reusable workflows the sole owners of native
packaging. Every distributable is described by a machine-verifiable target manifest, and release
assembly fails closed against an explicit six-target contract.

## 2. Goals

- Keep electron-builder and electron-updater as the packaging and update implementation.
- Build x64 and ARM64 packages on the repository's existing native GitHub-hosted runners.
- Reuse one Windows, one Linux, and one macOS packaging workflow from Build, Release, and package
  regression callers.
- Require every macOS Build and Release artifact to be signed, notarized, stapled, and verified.
- Keep Windows signing outside this change and never claim that Windows artifacts are signed.
- Keep pull-request and scheduled package regression unable to access Apple signing secrets.
- Replace permissive release collection with exact manifests, digests, updater metadata validation,
  and a public release index.
- Replace historical baseline rebuilds with a committed installer-size baseline and policy.
- Keep the fast PR quality gate independent from native packaging latency.
- Run complete package verification only for operating systems affected by packaging or runtime
  changes, while retaining full six-target regression on schedule and on demand.

## 3. Acceptance Criteria

### AC-1 — Reusable Native Packaging

- `_package-windows.yml`, `_package-linux.yml`, and `_package-macos.yml` are the sole owners of their
  operating system's runner mapping, native runtime installation, packaging, packaged smoke tests,
  and target artifact staging.
- Each reusable workflow accepts an immutable source SHA, an `x64` or `arm64` architecture, an
  `artifact-purpose` of `distribution` or `verification`, and an installer-size gate switch.
- Build and Release use `distribution`; package regression uses `verification`.
- Secrets are passed explicitly at every reusable-workflow boundary. `secrets: inherit` is absent.
- Called workflows declare their own required environment because caller workflow-level environment
  values are not inherited.

### AC-2 — Signing Boundary

- Every macOS package produced by `build.yml` or `release.yml` requires the signing certificate and
  Apple notarization credentials before expensive work starts.
- Distribution packaging verifies the signed and stapled application bundle and final DMG with
  `codesign`, `stapler`, `spctl`, and DMG integrity checks.
- The final nested CUA helper preserves its dedicated staging signature. Distribution verification
  requires its Developer ID authority, expected Team ID, hardened runtime, secure timestamp, exact
  entitlement allowlist, and allowed Mach-O load paths before accepting the outer application.
- The staged updater ZIP is extracted as the real updater consumer payload. Its sole root
  `DeepChat.app` must pass the same CUA helper and complete application distribution checks.
- `syspolicy_check distribution` assesses both the staging application and the application
  extracted from the updater ZIP after all electron-builder and notarization transformations.
- Verification packaging explicitly disables certificate auto-discovery, receives no Apple signing
  secrets, and never uploads a complete unsigned installer.
- Windows and Linux manifests contain no macOS distribution fields and no generic `signed` claim.
- Windows signing and certificate acquisition remain outside this goal.

### AC-3 — Target Manifest

- Each target manifest binds the package to one platform, architecture, source commit, application
  version, artifact purpose, toolchain version, check result, and exact file list.
- Manifest files include byte size and SHA-256 for every staged file.
- Only regular files inside the target staging root are accepted. Absolute paths, traversal,
  symlinks, duplicate basenames, duplicate roles, and unknown public files are rejected.
- A distribution manifest is generated only after package smoke and requested size gates pass.
- macOS distribution status is derived from actual verification commands, not caller-supplied
  booleans.
- Package manifest schema version 2 records final CUA helper and updater ZIP distribution
  verification separately from the staging application and DMG checks.

### AC-4 — Fail-Closed Release

- Release preflight resolves an existing lightweight or annotated tag to a commit and never falls
  back to the workflow context SHA.
- The tagged commit must be reachable from `origin/main`, match `package.json` version, and have a
  non-empty matching CHANGELOG section before any native package job starts.
- Assembly requires exactly the six supported targets: Windows, Linux, and macOS on x64 and ARM64.
- Every manifest must use the release source commit, version, and `distribution` purpose.
- Missing files, duplicate public names, unexpected targets, digest mismatches, incomplete macOS
  distribution evidence, or invalid updater metadata fail the release.
- Release index schema version 2 requires the CUA helper evidence for both macOS targets; version 1
  manifests cannot silently cross this strengthened verification boundary.
- Only the final draft-release job receives `contents: write`.

### AC-5 — Updater Compatibility

- Windows publishes `latest.yml` with x64 and ARM64 NSIS entries plus both EXE blockmaps.
- macOS publishes `latest-mac.yml` with x64 and ARM64 ZIP entries plus both ZIP blockmaps.
- DMG remains a manual installer and is excluded from updater metadata and blockmap generation.
- Linux publishes separate `latest-linux.yml` and `latest-linux-arm64.yml` files that reference the
  corresponding AppImage.
- Every metadata URL resolves to a staged release file, and its SHA-512 and byte size match that file.
- Windows and macOS entries are ordered x64 before ARM64, with legacy `path` and `sha512` fields
  pointing to x64.
- The workflow uploads exactly nineteen assets under the current package target configuration,
  including `release-index.json`.

### AC-6 — Package Regression

- Component budgets for OCR assets, Node, and other packaged runtimes remain part of every packaged
  Light OCR smoke test.
- Installer comparison reads committed baseline facts instead of rebuilding the historical commit.
- The initial baseline records successful run `29978292769` and source commit
  `dfb4ba0f34c008c27cfb6bd98a08fdbd36f7b343`.
- Windows EXE, Linux AppImage and tarball, and macOS ZIP and DMG enforce upper and lower delta bounds.
- `package-regression.yml` supports reusable, manual, and scheduled execution, always covers all six
  targets, and uploads reports rather than complete unsigned installers.
- A scheduled failure opens or updates one package-regression issue; the next fully successful
  scheduled run closes it.
- Scheduled and manually dispatched package regression remain independent from pull-request checks.

### AC-7 — Pull-Request Package Gate

- `prcheck.yml` owns only static analysis, complete main and renderer suites, Native Memory
  validation, the source build, and the stable `pr-required` aggregate.
- Both PR workflows target the repository's `dev` collaboration branch only. Release-to-`main`
  policy remains owned by the release workflow and release process, not routine PR checks.
- A separate `package-check.yml` starts for every PR targeting `dev`. It does not use workflow-level
  path filters, so its stable `package-required` result can safely be configured as a required check.
- The classifier is loaded only from the PR base revision, so a classifier-only change cannot use
  its candidate rules to skip its own package validation. If the base revision has no classifier
  during contract bootstrap, the gate validates both `package.json` snapshots and conservatively
  selects all six targets without executing candidate classifier code. Workflow changes remain
  protected by contract tests and normal review policy.
- Classification emits independent Windows, Linux, and macOS decisions with matched rule evidence.
  Invalid diffs, paths, output values, or job-result combinations fail closed.
- An affected operating system runs complete x64 and ARM64 verification, including every configured
  installer target, package smoke, component budgets, and the installer-size gate.
- Shared package configuration, native dependency, runtime, plugin, or package-contract changes run
  all six targets. OS-owned workflows, signing scripts, entitlements, installer scripts, and icons
  run only the corresponding operating system.
- The CUA Mach-O contract, final-helper verifier, and helper-signing path are macOS-owned package
  inputs and therefore trigger both macOS architectures.
- `package.json` is compared semantically: production dependency, Electron toolchain, package
  metadata, lifecycle, build, runtime, plugin, and package-smoke changes are relevant; test-only and
  unrelated development-tool changes are not. A lockfile change remains conservatively shared.
- Release-only assembly and preflight changes are covered by deterministic contract tests rather
  than unrelated native package jobs.
- `package-regression.yml` remains the full six-target nightly/manual safety net; it is not called by
  `prcheck.yml`.

## 4. Constraints

- Do not migrate to Electron Forge or universal macOS packages.
- Do not add dependency caching.
- Do not modify Windows signing, OAuth build-time credential behavior, or
  `windows-arm64-e2e.yml`.
- Preserve frozen pnpm installs and the second install required after `install:sharp`.
- Preserve current native runtime, Light OCR, DuckDB VSS, OpenDAL, CUA, and Feishu verification.
- Preserve one complete verification artifact contract for manifests, reports, and verification
  metadata. This does not require publishing complete unsigned installers; keep that publication
  prohibited and do not introduce a reduced updater-only verification set for PRs.
- Keep Actions pinned to immutable commit SHAs and checkout credentials disabled.
- Do not create or synchronize a GitHub issue.
- Do not push from this implementation branch.

## 5. Non-Goals

- Changing application runtime behavior or public application APIs.
- Publishing unsigned macOS verification artifacts.
- Replacing GitHub Releases or electron-updater.
- Introducing Windows certificates, package signing, or signing assertions.
- Refactoring the standalone Windows ARM64 E2E workflow.
- Changing branch-protection settings.

## 6. Compatibility and Risks

- The final metadata intentionally follows electron-builder 26 and electron-updater 6 selection
  semantics. Dependency upgrades must rerun the metadata contract tests before release.
- Package manifest and release index schema version 2 intentionally reject version 1 producers and
  consumers. All release jobs execute from one immutable source SHA, so no mixed-version migration
  path is supported or required.
- The exact nineteen-asset allowlist intentionally rejects new package formats until the contract,
  tests, and release index are updated together.
- Verification-mode macOS package sizes can differ slightly from signed distribution sizes. The
  initial 90 MiB delta bounds tolerate signing overhead while still catching material omissions.
- Native GitHub runner behavior and real Apple notarization cannot be proven locally. Verification
  mode has passed on all six native runners in Actions run `30013052661`; real distribution
  signing/notarization and draft publication still require a release or manual Build run.
- Run `30013052661` exercised the original nested PR caller. The separate, operating-system-scoped
  `package-check.yml` has deterministic local contract coverage but cannot be exercised on hosted
  runners until these commits are pushed.
- The package-impact classifier is intentionally conservative for shared packaging inputs, but it
  must not classify all workflows, generated registries, ordinary application code, or every
  packaged resource as native-package impact.
- Required-check configuration must include both stable aggregates, `pr-required` and
  `package-required`. The workflows cannot enforce repository rulesets themselves.
- A future breaking change to classifier output requires a staged migration because the base
  revision owns classification for anti-bypass behavior.

## 7. Open Questions

None. Tooling choice, signing boundary, target set, public asset set, PR gate topology, classification
boundary, and deferred work are fixed for this implementation.

---

### Architecture Ci Release Packaging Tasks (`docs/architecture/ci-release-packaging/tasks.md`)

# CI and Release Packaging Contract — Tasks

> Requirements are defined in [spec.md](./spec.md), and the implementation design is described in
> [plan.md](./plan.md).

## Architecture Record

- [x] Define the reusable OS package boundary and immutable caller interface.
- [x] Define macOS distribution signing and unsigned verification boundaries.
- [x] Define the six-target manifest and nineteen-asset release contract.
- [x] Define committed installer baseline and PR package-regression behavior.
- [x] Record excluded Windows signing, OAuth, caching, Forge, universal package, and ARM64 E2E work.
- [x] Record the decision not to create or synchronize a GitHub issue.

## Package Tooling

- [x] Add the shared target and file-role contract.
- [x] Add target manifest staging and validation.
- [x] Add installer baseline import and size comparison.
- [x] Add strict updater metadata and release assembly.
- [x] Add release preflight and package-impact classification.
- [x] Add focused fail-closed unit tests.

## Reusable Packaging

- [x] Add Windows x64/ARM64 reusable packaging.
- [x] Add Linux x64/ARM64 reusable packaging.
- [x] Add macOS x64/ARM64 reusable packaging with distribution verification.
- [x] Rewire manual Build to distribution-mode reusable workflows.
- [x] Protect workflow interfaces and runner mappings with parsed-YAML tests.

## Package Regression

- [x] Add reusable, manual, and scheduled six-target package regression.
- [x] Remove historical baseline rebuilds from package jobs.
- [x] Add the initial fail-closed PR impact classification.
- [x] Validate the initial conditional six-target PR gate before decoupling it.

## Pull-Request Gate Decoupling

- [x] Record the decision to keep complete per-target verification instead of adding an updater-only
  artifact mode.
- [x] Remove package classification and native regression from `prcheck.yml`.
- [x] Add an always-started `package-check.yml` with the stable `package-required` aggregate.
- [x] Classify Windows, Linux, and macOS impact independently with base-owned rules and evidence.
- [x] Ignore release-only tooling, unrelated workflows, generated registries, ordinary source, and
  documentation changes.
- [x] Protect fast and package aggregates with parsed-YAML and classifier contract tests.
- [x] Add installed electron-updater architecture-selection compatibility tests.

## Release

- [x] Move tag, ancestry, version, and CHANGELOG checks before native package jobs.
- [x] Rewire Release to distribution-mode reusable workflows.
- [x] Assemble only complete, digest-verified target manifests.
- [x] Generate canonical updater metadata and `release-index.json`.
- [x] Restrict write permission to draft release publication.
- [x] Remove tolerant copy and Ruby/YAML merge logic.

## Maintained Documentation

- [x] Update Light OCR package-size ownership.
- [x] Update Linux ARM64 metadata ownership.
- [x] Update release flow and plugin packaging guidance.

## Validation

- [x] Run focused package and workflow contract tests.
- [x] Run complete main and renderer suites.
- [x] Run type checking and the canonical build.
- [x] Run format, localization, lint, and final format checks.
- [x] Review generated provider and ACP registry refreshes.
- [x] Verify all six native verification packages on GitHub-hosted runners.
- [ ] Verify real macOS distribution signing and draft release publication.

### Validation Evidence

- Focused package/workflow/updater contracts: 6 files and 51 tests passed.
- The local main suite reached 425 passing files and 4,889 passing tests, but retained nine
  reproducible failures in three unrelated database-migration and scheduler files. These paths are
  unchanged by the PR-gate commits; the target-branch merge ref passed its complete `test-main` job
  in Actions run `30013052661`.
- Renderer suite: 197 files and 1,561 tests passed.
- Full type checking and the canonical production build passed.
- `actionlint` 1.7.12 accepted `prcheck.yml` and `package-check.yml`.
- Format, localization, lint, and final format checks passed.
- The canonical build left provider and ACP registry metadata unchanged.

GitHub Actions run `30013052661` passed the fast PR checks and all six unsigned verification targets
on their native runners. It predates the separate `package-check.yml`; the new scoped caller and the
memory-report warning fix require a pushed run for hosted validation. Real Apple distribution
signing/notarization and draft-release publication remain unverified because they require a release
or manual distribution Build run.

---

### Architecture Cua Driver 0 17 Contract Migration Spec (`docs/architecture/cua-driver-0-17-contract-migration/spec.md`)

# CUA Driver Contract

## Status

The bundled driver pin is `0.19.2` with embedded contract `0.6.0`. Runtime ownership, strict catalog
validation, snapshot-safe addressing, and bounded model projections are implemented. Native
cross-platform behavior and release-signed/notarized macOS remain explicit validation gates; the
current version pin does not imply that those gates have passed.

## Contract

DeepChat generates its native tool catalog from the pinned release binary and requires exact
platform-scoped policy coverage. Model-visible content is separate from raw MCP `structuredContent`.
Successful action tools use the closed `ActionResult` shape. Native element actions require an
opaque `element_token` or the exact `element_index + snapshot_id` pair from one current window
snapshot; a bare index is rejected before dispatch.

The maintained tool surface includes `verify_state`, `set_window_frame`, `invoke_menu`,
`clipboard_read`, and `clipboard_write` with the explicit policies below. Driver changes must preserve
runtime supervision, integrity verification, signing, and the supported target matrix. A delivered
action alone is never evidence that the user's requested postcondition is satisfied.

## Non-goals

- Do not add Linux arm64 support.
- Do not change the embedded daemon/proxy lifecycle or generic MCP result contract.
- Do not persist a DeepChat-owned “latest snapshot” cache or auto-inject a snapshot id.
- Do not expose raw clipboard plaintext to the model or add a new sensitive-data persistence
  path.
- Do not make `verify_state` a desktop or visual-image interpretation engine.
- Do not infer task completion from a delivered action.
- Do not sync this SDD to a GitHub issue unless explicitly requested.

## Pinned Upstream Contract

The runtime pin is:

- tag: `cua-driver-rs-v0.19.2`;
- commit: `20bb34b16ad7c6c56221c332e46b1875e9d8af8c`;
- driver version: `0.19.2`;
- contract version: `0.6.0`;
- tools-list schema version: `1`;
- capability version: `1`;
- MCP protocol version: `2025-06-18`.

The five currently supported targets remain `darwin/arm64`, `darwin/x64`, `win32/x64`,
`win32/arm64`, and `linux/x64`. `linux/arm64` remains explicitly unsupported even if upstream
publishes an asset.

## Tool Policy

Every target-local catalog tool must have one explicit policy entry after platform scoping. The following cross-platform tools use these defaults:

| Tool               | Policy  | Reason                                                                                                |
| ------------------ | ------- | ----------------------------------------------------------------------------------------------------- |
| `verify_state`     | `allow` | Bounded, read-only observation of one exact window                                                    |
| `set_window_frame` | `ask`   | User-visible window mutation                                                                          |
| `invoke_menu`      | `ask`   | User-visible native action that can trigger consequential commands                                    |
| `clipboard_write`  | `ask`   | Mutates privacy-sensitive shared system state                                                         |
| `clipboard_read`   | `deny`  | Can return privacy-sensitive plaintext that DeepChat currently persists as raw MCP structured content |

Explicit denial keeps the tool in the closed catalog and policy while preventing an accidental
sensitive-data path. Enabling reads later requires a separate design for consent, bounded
model-facing projection, transcript persistence, export, and retention.

## Snapshot-safe Element Addressing

The affected native tools are `click`, `double_click`, `right_click`, `type_text`, `press_key`,
`set_value`, and `scroll`.

DeepChat must preserve these invariants:

- prefer the non-empty opaque `element_token` from the latest `get_window_state` for the same
  process and window;
- an index fallback is valid only as the pair `element_index + snapshot_id` from that same result;
- a bare `element_index` fails locally before the runtime call;
- an empty or whitespace-only optional token is still removed so it cannot override another
  addressing mode;
- the adapter never guesses, caches, or injects a “latest” snapshot id;
- pixel-coordinate actions remain valid when no `element_index` is supplied;
- conflicting token/index/snapshot/window inputs are left for the pinned runtime to reject;
- an addressing refusal causes at most one fresh snapshot and one retry with handles from that
  new snapshot. No field from the rejected snapshot may be reused.

`get_window_state` model projection must label the exact pair explicitly. The projected token map
is capped at 256 bounded handles and does not duplicate the accessibility tree. When the map is
truncated, an unlisted element remains addressable through its same-result index-plus-snapshot
pair. Only the pinned runtime's lexical snapshot/token forms whose snapshot and element-index parts
agree with the structured row enter model-visible content; this boundary check does not decode,
derive, or synthesize either opaque handle for a caller.

## ActionResult Projection

Successful upstream action tools return:

- required `effect`: `confirmed`, `partial`, `unverifiable`, `suspected_noop`, or `refused`;
- required `route`: `accessibility`, `synthetic_events`, `global_input`, `system_api`, `dom`, or
  `trusted_input`;
- optional `delivery.mode` and non-negative `delivery.delivered_count`;
- optional evidence kinds `value_readback` and `window_change`;
- optional closed escalation target/reason.

DeepChat preserves the raw structured result for protocol fidelity and app diagnostics, then
appends a typed, bounded projection to model-visible content only for the reviewed action-tool
set. The projection accepts only closed enum values and numeric bounds. It emits no arbitrary
runtime strings and deduplicates the two possible evidence kinds. A non-error action response that
does not satisfy the reviewed shape appends a fixed contract-validation warning, so legacy result
text cannot silently become evidence of success.

Semantics:

- `confirmed` means the driver has action-specific evidence, not that the user's whole task is
  complete;
- `partial`, `unverifiable`, and `suspected_noop` require observation or recovery before another
  consequential step;
- `refused` is not success;
- delivery only describes dispatch and must not be treated as effect or task completion;
- escalation is advice constrained by current session policy, not permission to broaden scope.

Structured refusal projection remains supported for error results such as stale or malformed
snapshot handles, because those errors occur before a successful `ActionResult` is published.

## Verification Projection and Loop

`verify_state` verifies one exact `(pid, window_id)` against one to eight bounded predicates. Its
aggregate status is `satisfied`, `unsatisfied`, or `unknown`, and `unknown` never means success.

DeepChat projects only:

- aggregate `status`, `stable`, `elapsed_ms`, and `samples`;
- at most eight predicate indices, statuses, and closed `unknown_reason` values.

The upstream `observed_json` field is deliberately not promoted because it can contain application
text and is unnecessary for control-flow decisions. Raw structured content remains available to
the product diagnostics path. A non-error `verify_state` response that violates this shape appends
a fixed warning and cannot be interpreted as verification success.

The Computer Use loop uses `verify_state` after an action only when the requested postcondition is
expressible as a window existence/bounds predicate or trusted native element existence/value/
enabled/selected predicate. It treats success as `status="satisfied"` and `stable=true`. For
desktop scope, browser DOM, canvas, video, screenshots, or other visual effects, it uses a fresh
`get_desktop_state`, `get_browser_state`, or `get_window_state` as appropriate instead.

## Packaging and Compatibility

The existing release-asset staging and integrity model remains unchanged:

- verify `checksums.txt` and each selected archive SHA-256;
- validate the archive layout and executable identity;
- generate the target-local catalog using `dump-docs --type mcp --pretty`;
- scope platform-specific policy entries before exact catalog comparison;
- keep the DeepChat-owned macOS helper name, bundle id, entitlement allowlist, load-path checks,
  re-signing order, and notarization gates;
- keep only the unsigned primary Windows driver and the existing Linux binary layout.

The version/contract values intentionally remain duplicated in source manifest and packaging
validation so packaging fails if the source declaration drifts from the reviewed host contract.

## Acceptance Criteria

- The exact `0.19.2/0.6.0` embedded handshake starts; older or mismatched metadata is rejected.
- The five policy-listed tools are present, and target-local package policy equals
  the generated catalog exactly.
- Empty tokens are removed, bare indices fail before dispatch, and index-plus-snapshot or token
  inputs are preserved unchanged.
- Model-visible action and verification projections are closed, bounded, injection-resistant, and
  covered for valid and malformed inputs; malformed non-error results fail closed visibly.
- The skill requires current snapshot handles, distinguishes delivery/effect/completion, and uses
  `verify_state` only for supported window predicates.
- Focused adapter, ToolManager, plugin, embedded-runtime, catalog, integrity, and packaging tests
  pass.
- Formatting, i18n validation, lint, Node/Web type checks, CUA plugin validation, and a host-native
  CUA bundle/verify run pass where the current machine can execute the release binary.
- Native Windows/Linux behavior, macOS x64, and release-signed/notarized macOS remain explicit
  release gates unless run in their matching environments.

## Pin Consistency

The version/contract declaration, upstream release hashes, closed tool policy, model projections,
snapshot argument guard, Skill instructions, and generated catalog form one compatibility unit.
Mixed binary and host-contract versions must fail closed rather than degrade silently.

---

### Architecture Deepchat Agent Harness Boundaries Plan (`docs/architecture/deepchat-agent-harness-boundaries/plan.md`)

# DeepChat Agent Harness Boundaries Plan

## Current Slice

Replace the untyped hook fan-out with a typed deterministic notification pipeline. The typed tool
execution contract, the coordinator ownership slice, and the Harness facade below are complete and
remain retained records.

## Typed Hook Notification Pipeline Plan

### Target Layout

```text
src/main/hook/
├── events.ts     # canonical HookEvent union with coverage and rejection guards
├── observer.ts   # isObserved plus typed notify
├── index.ts      # subscription index, synchronous projection, per-session delivery
└── routes.ts     # configuration reads and writes through the service

src/main/agent/deepchat/runtime/
└── runtimeHookSink.ts  # scope factory plus the single terminal projection
```

### Stage 1: Contract

Add the `HookEvent` union and the two compile-time guards, then replace the observer interface. The
shared settings module, the renderer surface, and the version 1 payload stay untouched.

### Stage 2: Delivery

Give `HookService` ownership of its configuration and derived subscription index, refreshed
atomically on every write path. Project and truncate synchronously, clone only the permission
record, and serialize delivery per session.

### Stage 3: Producers

Turn `RuntimeHookSink` into a scope factory, unify the terminal projection, and migrate the loop,
turn, interaction, and ACP compatibility producers. Drop the ports each producer no longer needs.

### Validation

```bash
pnpm exec vitest run --config vitest.config.ts \
  test/main/hook \
  test/main/agent/deepchat/runtime/runtimeHookSink.test.ts \
  test/main/agent/deepchat/runtime/process.test.ts \
  test/main/agent/deepchat/runtime/dispatch.test.ts \
  test/main/session/runtimeIntegration.test.ts \
  test/main/routes/dispatcher.test.ts \
  test/main/app/compositionBoundaries.test.ts
pnpm run typecheck
pnpm run format
pnpm run i18n
pnpm run lint
pnpm test
```

Verify both compile-time guards by temporarily removing an event variant and by weakening a
required fact, confirming each fails `typecheck:node` before restoring the contract.

## Harness Facade Plan

### Target Layout

```text
src/main/agent/deepchat/
├── harness/
│   ├── createDeepChatAgentHarness.ts   # single composition root
│   ├── deepChatAgentHarness.ts         # delegation against existing ports only
│   ├── pendingInputWakeupBinding.ts    # the only explicit late binding
│   └── index.ts
└── runtime/
    ├── sessionIdentityService.ts
    ├── sessionStateResolver.ts
    ├── sessionLifecycleCoordinator.ts
    ├── transcriptMutationCoordinator.ts
    ├── messageProjectionService.ts
    ├── promptAssemblyService.ts        # binds resources/systemPromptBuilder
    ├── toolRuntimeBindings.ts          # binds tool result normalization and permission review
    └── streamRequestId.ts              # shared by run lifecycle and message projection
```

Bindings live under `runtime/` rather than beside the domain functions they bind because
`resources/` is excluded from the repository formatter by the shared `resources` ignore pattern.
The domain implementations stay where they are.

### Stage 1: Specify

Extend this architecture record with the facade slice, the owner map, the port narrowing rules, the
single permitted late binding, and the four load-bearing sequences that must move verbatim. Freeze
the zero-behavior-change constraint before touching source.

### Stage 2: Extract Runtime Services

Move identity, state resolution, session lifecycle, transcript mutation, and message projection out
of the root into named owners. Extract `resolveStreamRequestId` into a pure helper so message
projection and run lifecycle no longer form a cycle. Add the bound prompt assembler factory and bind
tool result normalization and permission review through named ports.

The root keeps its public surface in this stage and delegates to the new owners, so the change is a
pure move with an unchanged external contract.

### Stage 3: Narrow Runtime Ports

Replace registry-shaped callbacks (`getDeepChatInstance`, `getHydratedDeepChatInstance`,
`getRuntimeState`, `assertCurrent`) with a narrow `SessionScopeRegistry` and `SessionRuntimeScope`
usage. Replace remaining owner callbacks with concrete collaborators. Compose the six Tape
capabilities into one domain port. Introduce the named pending-input wakeup binding and remove every
other deferred wiring.

### Stage 4: Detach Runtime Instances

Delete `DeepChatAgentInstanceDelegate`, the registry hydrator, and `DeepChatAgentRuntime.dispose()`.
Add the `send` entry point that absorbs queue routing to the runtime public surface, and move the
manager backend to reach send, cancel, snapshot, and close through that surface.

### Stage 5: Add Harness Facade

Add the composition factory and the facade. The factory constructs the graph in dependency order and
returns owners; the facade holds those owners and implements `DeepChatAgentBackendPort`,
`SessionStatePort`, and `SessionTranscriptRuntimePort` with explicit `implements` clauses.

### Stage 6: Retire Runtime Root

Delete `deepChatRuntimeCoordinator.ts`. Migrate `src/main/app/composition.ts`, the ACP compatibility
dependency factory, session deletion, and transcript mutation wiring to the harness.

### Stage 7: Enforce Boundaries

Split the root test suite into owner suites and one full-runtime integration suite. Replace the
root-specific guard rules with harness rules: runtime modules must not import the harness layer, the
facade must not call protected implementation symbols, and the facade keeps a much smaller size
ceiling. Regenerate `docs/architecture/baselines/agent-system-layered-runtime-baseline.json` and
update the Tape capability distribution entry in `docs/architecture/tape-layering/spec.md`, which
names the retired root as the composition root.

### Harness Facade Validation

Run in increasing scope after every stage:

```bash
pnpm exec vitest run --config vitest.config.ts \
  test/main/agent/deepchat \
  test/main/session/runtimeIntegration.test.ts
pnpm run typecheck:node
pnpm run test:main
pnpm run lint:agent-cleanup
pnpm run format
pnpm run i18n
pnpm run lint
pnpm run build
```

Regenerate `pnpm run architecture:baseline` only in the final stage, after the source layout is
stable. If an environment-gated failure occurs, reproduce it on `dev` before classifying it as
unrelated.

### Harness Facade Commit Strategy

1. `docs(agent): specify harness facade`
2. `refactor(agent): extract runtime services`
3. `refactor(agent): narrow runtime ports`
4. `refactor(agent): detach runtime instances`
5. `refactor(agent): add harness facade`
6. `refactor(agent): retire runtime root`
7. `test(agent): enforce harness boundaries`

Every commit must compile and pass its focused suites. Before each commit, inspect staged and
unstaged changes for hidden side effects, compatibility breaks, edge cases, performance and security
risk, misleading names, missing tests, and future maintenance cost, then fix every finding and
rerun the relevant validation. Do not push.

## Coordinator Ownership Plan

### Stage 0: Remove Private Test Reflection

Inventory every `as any` or `as unknown as` access to a private coordinator member. Replace tests
with one of these seams, in priority order:

1. existing public runtime behavior;
2. observable event, persisted fact, or Session state;
3. a focused production owner constructed through typed ports once that owner exists;
4. a typed test dependency supplied through an existing constructor port.

Do not add public production methods solely for tests. This stage changes no production source and
must pass the full main-process suite before runtime extraction begins.

### Stage 1: Freeze Behavior

Add characterization coverage for:

- the exact four-sink status publication sequence;
- initial and resume returned/thrown/aborted settlement;
- stale instance, stale run, and replacement-controller fencing;
- steer-before-queue selection and every claim disposition;
- blocked attachments and OCR `attachmentPreparation` return mapping;
- pending interaction resume and cancellation;
- Memory callback ordering and exactly-once settlement.

### Stage 2: Scope And Stateless Policies

Add minimal scope creation to `DeepChatAgentRuntime`. Extract status publication, pre-stream
watchdog behavior, and pure provider context-budget decisions. Keep project resolution, effective
generation settings, Agent identity, hooks, and storage access as explicit ports.

### Stage 3: Run Lifecycle

Introduce `RunLifecycleCoordinator` as the only public owner for operation controllers, active runs,
cancellation, status/terminal projection, and queue wakeup. Preserve separate named predicates for
instance, run, controller, and message ownership. Route initial, resume, interaction, provider
permission, loop-ready, and manual-compaction fences through the matching predicate without
changing their conditions.

This is the highest-risk stage and remains its own commit and rollback point.

### Stage 4: Admission And Pump

Move input acceptance and mutation commands into `PendingInputAdmissionCoordinator`. Move queue
selection, single-flight, claim operations, failure recovery, and turn starting into
`PendingInputPump`.

Define an internal discriminated `TurnCompletion` / claimed-input disposition contract. Apply
transcript, compaction, and Memory rollback before releasing a retryable claim. Wire the real
turn/pump feedback loop through narrow synchronous interfaces rather than concrete imports or an
event bus. Delete both existing copies of claim release and drain scheduling helpers.

### Stage 5: Compaction And Composition

Extend `CompactionRuntimeCoordinator` with manual state and lifecycle entry points. Reduce
`DeepChatRuntimeCoordinator` to the existing public compatibility surface and composition. Extract
factory wiring only when it passes concrete owners or narrow ports rather than recreating the old
parent callback graph.

Split owner-specific tests out of the monolithic coordinator suite while retaining a compact
full-runtime integration suite.

### Stage 6: Architecture Enforcement

Replace the line-count-only signal with ownership checks that prevent owner modules from importing
the concrete coordinator and prevent lifecycle, pending-input drain, and manual compaction logic
from returning to the root. Keep an appropriate coordinator size ceiling as a secondary guard.
Regenerate the layered-runtime architecture baseline after the final source layout stabilizes.

### Stage 7: Review Corrections

Audit the complete branch diff against `dev`. Document the three explicit fixes and five ownership
corrections in the specification, then pin each changed invariant with focused coverage. Treat loss
of a pending interaction during concurrent terminal settlement as ownership loss rather than a new
resume failure, preserve the original claim-settlement error when durable verification also fails,
and keep diagnostic stages aligned with their actual owner.

Cache the minimal runtime scope by instance and share one parsed transcript snapshot between
question-follow-up and pending-interaction admission checks. Replace regex ownership checks with
TypeScript AST checks whose protected symbols are verified against their owner files, so comments
do not trigger false positives and symbol renames fail closed.

### Stage 8: Pull-Request Review Hardening

Replace the pending-input Boolean single-flight marker with an atomic, token-owned drain lease that
is acquired before the first asynchronous state read. Coalesce wake reasons that arrive after the
launched claim has settled and its resulting Session status admits the reason, then replay them
after exact-owner release without duplicating durable queue state. Continue to leave ordinary
enqueues pending when the active turn ends in error.
Keep cleanup non-hydrating, terminalize every assistant message represented by recovered pending
interactions, and stop terminal persistence from referencing messages removed by retry rollback.
Pin each path with focused owner or full-runtime coverage.

Standardize recovery logging through the redacting logger, centralize attachment-lane keys and
project-directory mutation, and replace fixed microtask waits with observable async completion.
Retain explicit typed Memory test delegation rather than hiding interface drift behind a Proxy.

## Coordinator Ownership Validation

Run validation in increasing scope after every stage:

```bash
pnpm exec vitest run --config vitest.config.ts \
  test/main/agent/deepchat/instance/deepChatAgentRuntime.test.ts \
  test/main/agent/deepchat/runtime/deepChatRuntimeCoordinator.test.ts \
  test/main/session/runtimeIntegration.test.ts
pnpm run typecheck:node
pnpm run test:main
pnpm run format
pnpm run i18n
pnpm run lint
pnpm run build
```

Focused owner suites replace or supplement the first command as they are introduced. Run the
architecture guard and regenerate its baseline in the final stage. If an environment-gated failure
occurs, reproduce it on `dev` before classifying it as unrelated.

## Completed Typed Tool Execution Contract

### Type Model

1. Add `ToolEffect`, `ToolExecutionMode`, and the discriminated `ToolExecutionContract` union to the
   canonical core MCP types.
2. Add one required `execution` object to `MCPToolDefinition` so capability metadata remains atomic
   and namespaced from model-visible definition fields.
3. Export one deeply frozen `TOOL_EXECUTION` preset catalog for parallel read, sequential read, and
   sequential write contracts so shared preset references cannot be mutated at runtime.
4. Replace the duplicate `MCPToolDefinition` declaration in the broad shared MCP module with a type
   alias and re-exports from the canonical module.

This keeps one source of truth while preserving both existing import paths.

### Catalog Classification

Add an explicit contract at every production definition boundary:

- filesystem `read`: parallel read;
- filesystem `glob` and `grep`, Tape query tools, `skill_list`, and browser status: sequential read;
- every other built-in Agent tool: sequential write;
- MCP and plugin definitions: sequential write at the MCP discovery boundary, independent of MCP
  annotations.

Use a tool's maximum capability when its effect depends on arguments. Keep these classifications
close to definition construction rather than introducing a second name-to-policy registry.
Prompt-only fallback descriptors remain base definitions and do not fabricate execution metadata.

### Runtime Policy

Create a small pure module in the DeepChat runtime that selects a batch execution mode from:

- Session permission mode;
- ordered completed tool calls;
- current tool definitions.

Build a definition-name index once per decision. Treat duplicate names as ambiguous and fail closed.
Return `parallel` only for a multi-call, `full_access`, all-parallel-read batch. Integrate this
selector at the existing parallel branch in `dispatch.ts` without changing execution or commit
mechanics.

### Provider And Context Boundaries

Keep provider mapping explicit: AI SDK and legacy prompt paths consume function metadata only.
Change the DeepChat token estimator to measure the historical definition projection without
the complete `execution` object. Use the same projection for Tape ViewManifest tool-definition hashes
so execution policy does not redefine provider-view identity. Add regression coverage proving
execution metadata neither reaches the AI SDK tool schema nor changes the existing reserve or hash.

### Tests

Add a focused pure-policy suite for:

- homogeneous parallel reads;
- sequential reads and mixed effects;
- non-`full_access` modes;
- missing, malformed, and duplicate definitions;
- single-call batches.

Extend dispatch coverage to prove the policy drives real concurrency and serialization without
changing result order or failure isolation. Extend MCP catalog coverage to prove `readOnlyHint`
does not grant concurrency. Update production and test definition fixtures to satisfy the canonical
type.

### Validation

Run in increasing scope:

```bash
pnpm exec vitest run --config vitest.config.ts \
  test/main/agent/deepchat/runtime/toolExecutionPolicy.test.ts \
  test/main/agent/deepchat/runtime/dispatch.test.ts \
  test/main/agent/deepchat/runtime/contextBuilder.test.ts \
  test/main/provider/aiSdkToolMapper.test.ts \
  test/main/mcp/toolManager.test.ts \
  test/main/tool/toolService.test.ts \
  test/main/session/data/tapeViewManifest.test.ts
pnpm run typecheck
pnpm run format
pnpm run i18n
pnpm run lint
pnpm run test:main
```

If the full main suite has unrelated environment-gated failures, reproduce them on `dev` and report
the distinction rather than weakening coverage.

## Commit And Review Strategy

Use independently reviewable local commits for the ownership slice:

1. `docs(agent): specify runtime ownership`
2. `test(agent): remove runtime private coupling`
3. `test(agent): freeze runtime ownership behavior`
4. `refactor(agent): scope runtime identity`
5. `refactor(agent): own run lifecycle`
6. `refactor(agent): own pending input flow`
7. `refactor(agent): complete runtime ownership`

Before each commit, inspect both staged and unstaged changes for hidden side effects, compatibility,
edge cases, performance, security, misleading names, missing tests, and future maintenance cost.
Fix all findings and repeat the relevant validation before committing. Do not push these commits.

The final build may refresh `resources/acp-registry/registry.json`; this is the repository's expected
build preflight maintenance and must be identified as such in pull-request handoff rather than
silently reverted.

## Later Slices

Hook decision semantics remain unscheduled. Letting a hook block, replace, cancel, or rewrite a
provider request or tool arguments needs a versioned stdout protocol, a conflict and timeout policy,
argument revalidation, a permission recheck after rewriting, and a trust surface in settings. That
is a product feature with its own security model and must not be folded into a delivery contract.
Same-run steering remains a separate feature design.

---

### Architecture Deepchat Agent Harness Boundaries Spec (`docs/architecture/deepchat-agent-harness-boundaries/spec.md`)

# DeepChat Agent Harness Boundaries

## Context

DeepChat's tool runtime already has a conservative parallel fast path, but the scheduling policy is
encoded in `dispatch.ts` as an Agent tool-name allowlist. That makes execution safety depend on a
string convention instead of the tool catalog that owns tool capabilities. A rename, replacement,
or new read-only tool therefore requires runtime knowledge that does not belong in dispatch.

This architecture goal establishes stable contracts around the DeepChat Agent harness over
multiple short-lived pull requests. The typed tool execution contract, the coordinator ownership
slice, and the Harness facade are complete. The current slice replaces the untyped hook fan-out with
a typed deterministic notification pipeline. Same-run steering remains a separate change, and hook
decision semantics remain an unscheduled product feature rather than part of this goal.

## Comparative Evidence

Pi places an optional `executionMode` on each Agent tool. Its loop executes a batch in parallel by
default unless global configuration or any tool requests sequential execution. This correctly puts
the capability on the tool, but its default-open policy is too permissive for DeepChat's mixed
built-in/MCP catalog, permission pauses, durable queue, and persisted tool settlement.

Bub's asynchronous executor applies `asyncio.gather` to every tool-call batch. It preserves input
result order and isolates declared tool errors, but it has no side-effect or scheduling contract.
That model cannot safely represent DeepChat tools that mutate files, settings, memory, Tape-visible
state, browser state, processes, or user interactions.

DeepChat will keep its existing fail-closed, all-or-nothing batch behavior while moving the
parallel decision from a name allowlist to an explicit catalog-owned contract.

Pi's useful runtime boundary is not its directory layout but the separation between its pure agent
loop, in-memory Agent lifecycle, persistent Harness, and Session facts. DeepChat already has the
equivalent lower layers in `DeepChatLoopEngine`, `LoopRun`, `DeepChatLoopRunner`,
`DeepChatAgentInstance`, and Session data. The remaining problem is the application orchestration
above them: lifecycle, pending-input admission, queue pumping, compaction, and projection still
route through one coordinator.

Bub likewise separates framework turn processing, Agent execution, and Tape, but its hook-first
pipeline does not model DeepChat's durable pending-input queue, interaction recovery, instance
replacement fencing, or renderer projection. This slice therefore adopts the ownership lesson,
not Bub's hook pipeline or execution semantics.

## Coordinator Ownership Slice

### Problem

`DeepChatRuntimeCoordinator` remains both composition root and implementation owner. Its child
coordinators depend on broad callback surfaces that route back to private methods on the parent.
Pending-input claim and drain mechanics are duplicated between the root and `TurnCoordinator`,
while active-run cleanup, abort settlement, status projection, and queue wakeup are spread across
initial turns, resumed turns, interactions, and manual compaction.

The result is structural rather than cosmetic coupling: changes to one lifecycle invariant usually
require coordinated edits across `DeepChatRuntimeCoordinator`, `TurnCoordinator`, and
`DeepChatLoopRunner`. Tests compound the coupling by reflecting private coordinator members through
type escapes, so moving an owner can fail only at runtime instead of at compile time.

### Ownership Model

`DeepChatAgentRuntime` remains the sole registry of hydrated instances and creates a minimal
`SessionRuntimeScope`. The scope is an identity capability, not a service locator:

```ts
interface SessionRuntimeScope {
  readonly sessionId: AppSessionId
  readonly instance: DeepChatAgentInstance

  state(): DeepChatSessionState | undefined
  isCurrent(): boolean
  assertCurrent(): void
}
```

Project-directory resolution, generation-setting resolution, Agent identity, hooks, stores, and
provider capabilities remain explicit collaborator dependencies. The scope must not cache or
duplicate mutable facts already owned by `DeepChatAgentInstance` or Session data.

Portable plugin context hooks use a narrow `PluginContextPort`, separate from asynchronous
notification delivery. The Turn Coordinator admits full input before provider assembly;
committed compaction supplies its own boundary. Plugin hooks persist attributed results in Tape,
and the loop projects current `plugin_context` instructions using the accepted user message ID.
Projection and provider retries do not execute hooks. Disable revokes subsequent contributions;
provider authority rechecks projection before dispatch. Active-run/update exclusion protects
revision replacement. Context output has no permission, request-rewrite or tool-decision channel.
The supported lifecycle and budgets are defined in [User Plugins](../../features/user-plugins/spec.md).

Lifecycle ownership has four distinct fence semantics and must not collapse them into one generic
`ownsTurn()` predicate:

1. **instance fence**: the scope's instance is still the registry's current instance;
2. **run fence**: a `runId` still identifies the instance's active `LoopRun`;
3. **operation-controller fence**: a pre-stream or compaction `AbortController` still owns the
   current operation slot;
4. **message association**: an interaction or permission belongs to the expected assistant message
   or run.

One lifecycle owner may implement these predicates, but callers must use the specifically named
fence that matches their operation.

### Runtime Owners

- `RunLifecycleCoordinator` owns pre-stream controllers, active-run registration and cleanup,
  deferred-tool cancellation, explicit cancellation, terminal settlement, stale-run protection,
  pending-interaction projection, status transitions, and post-settlement queue wakeup. Mutable
  state remains on `DeepChatAgentInstance` and `LoopRun`.
- `SessionStatusPublisher` is the single projection path for a status transition. It preserves the
  existing order: `sessions.status.changed`, `sessions.updated`, internal session update, then UI
  refresh.
- `PendingInputAdmissionCoordinator` owns send/steer normalization, attachment acceptance lanes,
  capacity and interaction gates, queue mutation commands, steer promotion, explicit Queue resume,
  and interrupt requests. Listing pending inputs is a pure read.
- `PendingInputPump` owns steer-before-queue selection, per-session single-flight drain, claim,
  release, consume, process-local restart holds, recovery, and starting a claimed turn. Durable
  input records remain owned by `SessionPendingInputs`.
- `TurnCoordinator` continues to own the distinct initial and resume preparation algorithms, but
  delegates lifecycle settlement and pending-input disposition instead of mutating those owners
  through parent callbacks.
- `CompactionRuntimeCoordinator` expands to own manual compaction state and operation lifecycle in
  addition to its existing compaction projection; no parallel manual-compaction coordinator is
  introduced.
- `DeepChatRuntimeCoordinator` becomes a compatibility adapter and composition root. Moving wiring
  into a factory is allowed only after the callback graph is reduced; moving the existing callback
  graph unchanged does not satisfy this architecture goal.

### Pending Input Completion

Internal turn execution returns a typed completion that explicitly carries the disposition of a
claimed pending input. The disposition distinguishes consume, release-before-user-fact, and
rollback-after-user-fact semantics. The pump applies queue-state mutations; transcript, compaction,
and Memory rollback remains an ordered turn-settlement operation.

The pump and turn lifecycle form a real feedback loop. They communicate through narrow synchronous
`TurnStarter` and `PendingInputWakeup` interfaces wired by the composition root. An asynchronous
event bus is not used because claim settlement and the next drain must observe a deterministic
order. Public `MessageStartResult`, including `attachmentPreparation`, remains unchanged and is
mapped losslessly from the internal completion.

### Pending Input Restart Boundary

Harness construction reconciles active pending inputs before transcript recovery and before any
Session scope is hydrated. `SessionPendingInputs` returns the affected Session IDs and Queue input
IDs that must be held. It terminalizes unread Steer messages, their Tape replacements, and their
pending rows atomically instead of handing them to a later global recovery phase.

The pump installs Queue IDs in a process-local hold set. Automatic `enqueue` and `completed` wakes
stop at a held Queue FIFO head; pending-input listing and Session hydration never schedule a drain.
An explicit composer Send may start without releasing historical drafts. The typed
`sessions.resumePendingQueue` operation validates normal Session gates, releases the Session hold,
and drains with a `manual` wake reason. Before that manually resumed turn enters the provider Run,
the pump consumes its Queue claim; a returned provider error therefore cannot restore the same item
to Queue. Manual intent is tracked by Queue item ID, so attachment block and resolution cannot erase
that boundary. The pending-input list projects authoritative resume availability, and Resume returns
`false` without draining when no hold exists. Failures before the provider boundary still release
safely. Durable Queue rows remain the only ordering and content source, and a later restart derives
a new hold from whatever rows remain.

An unclaimed Steer belongs to the previous process's interrupted delivery attempt. Startup changes
its linked messages to retryable `error`, appends their Tape replacements, and consumes its pending
row in one transaction. The renderer hides its receipt and adds no recovery-specific action; the
standard message toolbar remains unchanged. A claimed Steer keeps its sent user message and exposes
the interruption through the assistant error. Neither path adds a persisted recovery marker, age
policy, schema migration, or hydration-triggered execution.

### Test Boundary

Before production extraction, tests stop reflecting private coordinator members. Existing cases
must assert public behavior or construct the new owner through typed test ports. This test-only
stage changes no production source and must pass the complete main-process suite.

Production extraction preserves all observable assertions except for the twelve corrections listed
in the next section. Tests may move to owner-specific suites or replace structural mocks with typed
ports, but expected status, event order, queue disposition, terminal persistence, hooks, Tape,
Memory, permissions, and public return values outside those corrections must not change. Each
correction requires a regression test that distinguishes its old and new behavior.

### Included Runtime Corrections

This ownership slice contains three explicit bug fixes, five corrections exposed while moving the
owning code, and four additional ownership defects found during full pull-request review. They
remain in this pull request because later extraction rewrote or relocated the same control flow, so
separating them now would require replaying and resolving the complete ownership refactor without
restoring an independently reversible change.

| Correction | Previous behavior | Required behavior and rationale |
| --- | --- | --- |
| Deferred tool progress | A deferred tool resumed from the interaction's pre-execution block snapshot and could overwrite progress or terminal data written while the tool ran. | Re-read the assistant message after deferred execution and continue only while the same pending interaction still exists. This preserves concurrent progress and refuses stale ownership. |
| Follow-up admission | Concurrent direct sends answering a completed tool question could both pass admission before the first turn established its durable claim. | Serialize send acceptance and permit only one immediate follow-up claim; later sends observe the first claim and remain pending. |
| Rejected live-send rollback | Releasing a claimed live send after its user fact was persisted left a visible user message and derived compaction or Memory facts for a turn that never started. | Roll back transcript, compaction, and Memory facts before releasing the claim, so retry never duplicates a visible turn. |
| Steer marker ownership | Adopting any steer claim unconditionally cleared the instance's active steer marker, including a newer marker installed concurrently. | Clear the marker only when its id matches the adopted claim, preserving a newer merge target. |
| Direct-steer ownership | A false drain result caused deletion unless the narrow draining/generating checks happened to be visible at that instant. | Retain the steer whenever its durable state or an active generation, controller, drain, or generating projection proves another owner accepted it. |
| Promoted-steer ownership | A false drain result restored a promoted steer even when another drain had already claimed or consumed it. | Restore only when no durable or runtime owner accepted the promoted steer. |
| Operation-controller ownership | Cleanup without an exact controller reference could clear a replacement operation's controller. | Treat an absent expected controller as a no-op; only the exact current controller may be cleared. |
| Follow-up/drain overlap | A direct follow-up could attempt a second immediate start while another pending-input turn owned the single-flight pump. | Keep the new input pending while another owner holds the instance-scoped drain lease. At every normal asynchronous yield point the draining turn already owns a claimed row, so the claimed-input gate also prevents a second start. The explicit lease check protects defensive or re-entrant observations; once the in-flight turn persists its user fact, transcript ordering removes the old follow-up marker and normal completion wakeup admits the pending input. A regression test constructs that conservative overlap and verifies admission after the transition. |
| Atomic drain ownership | Concurrent `drain()` calls could both pass the Boolean single-flight check before either call marked the instance as draining, claim different rows, and later clear each other's state. A wake arriving while the owner finalized could also be lost. | Acquire an instance-scoped drain lease synchronously before the first asynchronous state read and release it only with the exact owner token. Non-starting paths release locally; a launched turn transfers release ownership to its finalizer. Coalesce transient wake reasons only after the active claim has settled and the resulting Session status admits that reason, then replay them after lease release. Enqueues accepted while a turn is still generating continue to rely on normal completion wakeup, so an error leaves their backlog pending. The durable queue remains the sole input fact. |
| Non-hydrating readiness cleanup | Clearing `firstTurnReady` used `getOrCreateScope`, so cleanup after eviction could recreate a runtime instance with no active operation. | Clear readiness only on an already hydrated scope. Cleanup is a no-op after eviction and cannot resurrect Session runtime state. |
| Multi-message interaction cancel | Canceling recovered pending interactions terminalized only the first assistant message before dropping every interaction reference. | Terminalize every distinct assistant message represented by the pending interaction set, then clear the set and emit one Session terminal projection and queue wakeup. |
| Rolled-back terminal references | A thrown pending-input turn rolled back its user and assistant messages but retained their IDs, then attempted error persistence and emitted stream failure for deleted records. | Clear both IDs immediately after durable transcript rollback, including returned error results, so no terminal write, event, or public result references a deleted message. |

The last rule deliberately does not let ordinary queue-origin inputs bypass the tool-follow-up gate.
`PendingInputEnqueueSource` remains the admission distinction. The production invariant is the
durable claimed row plus the instance's single-flight drain marker, so no additional persisted fact
or second source of truth is introduced.

## Coordinator Ownership Goals

- Establish one explicit owner for Session identity, run lifecycle, status projection, pending-input
  admission, queue pumping, turn settlement, and compaction orchestration.
- Replace parent-private callback surfaces with concrete collaborators or narrow typed ports.
- Preserve every public contract and observable runtime ordering while making owner tests
  independently constructible.
- Leave a stable internal ownership model for the later Harness facade and typed hook reducer.

## Harness Facade Slice

### Problem

The coordinator ownership slice extracted lifecycle, admission, pump, and compaction owners, but
`DeepChatRuntimeCoordinator` remains the composition root, the implementation site for every
remaining cross-cutting session concern, and the only object every owner can reach. Its constructor
contains 72 arrow functions, and the class body still implements session initialization and
destruction, session-state hydration, Agent identity resolution, transcript mutation coordination,
message refresh projection, subagent tool-call progress, system prompt assembly, tool result
normalization, and auto-approve permission review.

Because those implementations live on the root, every extracted owner must accept a callback that
points back into it. The dependency graph is therefore a cycle rather than a layered runtime:
root creates owner, owner holds a closure over a root private method, and that method reaches a
different owner. The consequences are structural:

- Owners are not independently constructible, so runtime coverage concentrates in one 12918-line
  suite instead of owner suites.
- `DeepChatLoopRunnerPorts` (32 members), `TurnCoordinatorPorts` (26 members), and
  `InteractionCoordinatorPorts` (13 members) are wide because they must carry root helpers rather
  than collaborators.
- `DeepChatAgentInstance` holds a `DeepChatAgentInstanceDelegate` that calls back into orchestration,
  so the runtime registry cannot become a pure state owner and the registry hydrator closes the
  largest construction cycle in the module.
- The root is at 1185 of its 1300-line architecture ceiling, so no further owner can be absorbed.

### Service Ownership

Each remaining root implementation moves to exactly one owner. New owners are ordinary classes with
concrete dependencies and no reference to the facade.

| Owner | Responsibility | Replaces |
| --- | --- | --- |
| `SessionIdentityService` | Agent identity resolution with instance cache and persisted fallback; ACP-backed subagent classification | `getSessionAgentId`, `isAcpBackedSubagentSession` |
| `SessionStateResolver` | Runtime-state read and database hydration, including pending-interaction status projection and missing-session eviction | `getResolvedSessionState`, `getSessionState`, `getSessionListState` |
| `SessionLifecycleCoordinator` | Session initialization and destruction ordering across settings, runtime state, compaction, Memory, durable queue, transcript, and tool mappings | `initSession`, `destroySession` |
| `TranscriptMutationCoordinator` | Clear, retry, truncate, and fork coordination against the transcript mutation contract | `prepareClearMessages`, `finishClearMessages`, `prepareRetry`, `cancelForTranscriptMutation`, `invalidateTranscriptFrom`, `finishTranscriptTruncate`, `resetForkTarget` |
| `MessageProjectionService` | Assistant message refresh projection and subagent tool-call progress persistence | `emitMessageRefresh`, `updateSubagentToolCallProgress` |

Three existing domain implementations gain bound entry points instead of moving.
`buildSystemPromptWithSkills` keeps its implementation in `resources/systemPromptBuilder.ts` and
gains a binding that owns `BasePromptAssembler` and `PostCompactionPromptAssembler` construction.
`normalizeToolResultContent` and `reviewAutoApproveToolPermission` stay domain functions and are
bound once through named ports at composition time. Mechanically wrapping a single function in a
class adds indirection without adding an owner.

Instance access and staleness fencing are registry reads, not lifecycle operations. Owners that
only need `getInstance`, `getHydratedInstance`, `getRuntimeState`, or `assertCurrent` take a narrow
`SessionScopeRegistry` (`getOrHydrateScope`, `getHydratedScope`, `scopeFor`) and use
`SessionRuntimeScope` directly. This removes those four callback shapes from every port surface and
deletes an artificial dependency on `RunLifecycleCoordinator`.

### Port Narrowing

Owners depend on concrete collaborators wherever construction order already permits it:
`TurnCoordinator` on `DeepChatLoopRunner`, `InteractionCoordinator` on `TurnCoordinator` and
`DeferredToolExecutor`, `PendingInputPump` on `TurnCoordinator`, and `DeepChatLoopRunner` on
`CompactionRuntimeCoordinator`. Narrow structural interfaces are retained where they express a real
contract, such as `PendingInputTurnStarter`, but they are satisfied directly instead of by anonymous
adapters.

The six Tape capability ports on `DeepChatLoopRunnerPorts` (`tapeReconciliation`,
`tapeViewManifestReader`, `tapeViewManifestWriter`, `tapeProviderAttemptReader`,
`tapeProviderAttemptWriter`, `tapeToolFactWriter`) become one composed domain port. Tape is a single
subsystem, and splitting one collaborator across six fields describes the capability types rather
than the dependency.

### Late Binding

Exactly one runtime cycle is real: run settlement wakes the pending-input pump, and the pump starts
turns through the lifecycle owner. It is expressed as one named binding module rather than an
anonymous closure, and it is the only permitted deferred wiring in the graph.

The `DeepChatAgentRuntime` hydrator cycle is not real and is removed rather than deferred.
`DeepChatAgentInstanceDelegate` exists only to give `DeepChatAgentInstance` the four methods the
manager backend calls. Routing those through the harness port makes the registry a pure state owner,
deletes the hydrator, and removes `dispose()`, which has no production caller.

### Facade Contract

The facade implements only contracts that already exist. It adds no new public vocabulary, no
subscription channel, and no state of its own:

- `DeepChatAgentBackendPort` for the manager backend, extended with one `send` entry point that
  absorbs the queue routing previously performed inside the instance delegate.
- `SessionStatePort` for session state and settings.
- `SessionTranscriptRuntimePort` for transcript mutation coordination.
- The ACP compatibility dependency factory, the tool registry refresh entry point, the Memory
  ingestion observer, and the runtime registry accessor.

The facade owns no phase machine, queue, cache, subscription, or derived fact, and every method
delegates to exactly one owner. Multi-owner sequences belong to `SessionLifecycleCoordinator` or
`TranscriptMutationCoordinator`. The ACP compatibility dependency factory is bound at composition
and reaches the facade as one pre-bound function rather than as raw infrastructure fields.

The harness barrel exports only `createDeepChatAgentHarness`, the facade type, and its dependency
types. The composed owner graph, its factory, and the pending-input wakeup binding stay
package-private, because exporting them would let a caller reach an owner around the facade and
would let a second graph run the restart-recovery side effects the factory performs. Deep imports
into the harness directory from outside it are rejected for the same reason.

### Zero-Behavior-Change Constraint

This slice changes no runtime behavior. Any defect found while moving code is recorded and deferred
to a separate branch rather than fixed in place, because a behavior change inside a graph rewrite
cannot be reviewed or reverted independently.

Four sequences are load-bearing and must move verbatim:

1. **State hydration side effects.** `hasPendingInteractions` is read before `setRuntimeState`;
   a missing database row evicts the runtime instance and returns null; Agent identity resolution
   writes back onto the instance; only the full hydration mode warms effective generation settings.
2. **Message refresh order.** `chat.stream.completed` publishes first, then the transcript message is
   read, then the internal session update publishes. A non-assistant or unparsable message returns
   after the first publication.
3. **Status publication order.** `sessions.status.changed`, `sessions.updated`, internal session
   update, then UI refresh.
4. **Destroy ordering.** Memory destroy begins, scope operations cancel, readiness clears, then
   durable pending inputs, transcript, and session settings are deleted, then owned state clears,
   the instance is evicted, Memory destroy finishes, and tool mappings clear.

The durable pending-input queue, Tape, Memory, and permission recovery keep their existing single
sources of truth. No fact is duplicated onto the facade or onto a new service.

One documented exception applies to source behavior only. The repository's build preflight rewrites
`resources/model-db/providers.json` and `resources/acp-registry/registry.json`, which changes model
availability, capability and price metadata, and executable ACP package versions. The refresh is
required maintenance and is kept, but it lands in its own commit so it is reviewable and revertible
independently, and ACP version bumps deserve the same supply-chain attention as any dependency
change.

### Final Review Corrections

The final review admits three narrow hardening changes without broadening the facade design:

| Finding | Disposition |
| --- | --- |
| Async Session settings ownership | `setModel`, `setAgentContext`, and `updateGenerationSettings` bind one `SessionRuntimeScope` across every await, recheck status/model identity before persistence, and never mutate a replacement instance. Focused tests cover replacement during provider-default resolution, Memory reassignment fencing, generation-setting sanitization, and skill revalidation. |
| Harness barrel AST completeness | The export-surface guard handles export assignments, enums, modules/namespaces, import-equals declarations, anonymous default declarations, and destructured variable exports. Unsupported names fail closed under the existing allowlist. |
| Pending-input rollback fence | `rollbackPendingInputTurn` requires the caller's already-held instance instead of silently hydrating a current instance through a default parameter. Both callers already provide that ownership token. |

Other review observations do not belong in this correction:

- The steer merge race remains deferred below because closing it changes queue/cancel product
  semantics.
- Compaction cleanup ordering predates this slice and is covered by the explicit requirement to
  retain stale-instance and transcript ordering. Changing it requires a separate complex-bug spec.
- Provider and ACP catalogs are external generated snapshots. Their current upstream sources still
  contain the reported model metadata, so local edits would be overwritten by the next build.
- `uvx` resolves package `requires-python` metadata with managed Python downloads; the ACP package's
  Python range is not constrained by DeepChat's host interpreter range.

### Deferred Findings

Defects observed while moving code are recorded here and fixed on a separate branch.

| Finding | Observation | Current handling |
| --- | --- | --- |
| Steer merge race | Merging two rapid steer inputs into one turn depends on an in-flight `steerActiveTurn` reaching the instance steer marker before a settlement-triggered drain adopts the claim. Nothing serializes the two, so the window is defined only by the async depth of the pump's session-state read. | Behavior preserved by keeping that read behind its own async boundary, with the reason recorded at the wiring site. Closing the race requires holding the steer lane across queue and cancel, which is a behavior change. |
| Duplicated drain logging | `RunLifecycleCoordinator.schedulePendingInputDrain` and `PendingInputPump.schedule` both catch a rejected drain and log the identical `drainPendingQueueIfPossible` message, so one owner is redundant. | Left as is; both paths keep their existing coverage. |

### Test Boundary

Every extracted owner gets a focused suite that constructs it directly through typed dependencies
instead of building the whole runtime. Assertions move rather than change; total executed test count
must not drop.

The former root suite is retained as the full-runtime integration suite and renamed with the
harness. Moving its owner-specific describe blocks into the owner suites is deferred: the file
declares four module-scope `vi.mock` factories that Vitest hoists per file, so splitting it requires
restructuring the mock wiring rather than moving text. That restructuring is its own change and must
not be smuggled into a graph rewrite.

## Harness Facade Goals

- Give every remaining root implementation exactly one named owner outside the facade.
- Make the runtime dependency graph one-directional, with a single named late binding.
- Make `DeepChatAgentInstance` and `DeepChatAgentRuntime` pure runtime-state owners.
- Reduce the facade to delegation against contracts that already exist.
- Prevent re-accretion with an architecture guard that fails on runtime modules importing the
  harness layer and on protected implementation symbols reappearing in the facade.
- Leave a stable service layer for a later typed hook reducer without exposing that layer as a
  service locator.

## Typed Hook Notification Pipeline Slice

### Problem

Hooks are user-configured shell commands that observe the Agent lifecycle. The contract that reaches
them is a single flat `HookContext` whose fields are all optional, so the event name and its payload
are uncorrelated. `dispatch('Stop', { tool })` compiles, `dispatch('PreToolUse', { sessionId })`
compiles, and adding a name to `HOOK_EVENT_NAMES` fails no build.

That weak contract produced measurable defects:

- Every dispatch site hand-assembles the session envelope, and the `Stop` plus `SessionEnd` pair has
  four independent implementations in `runtimeHookSink`, `turnCoordinator`, `interactionCoordinator`,
  and the ACP compatibility path. The ACP copy already dropped `usage`.
- `HookService` re-read `hooksNotifications` on every event. That key is a sensitive app setting, so
  each read is a database read plus a JSON deep clone, a Zod normalization, and two `JSON.stringify`
  calls, performed twice per tool call even when no hook is configured.
- A tool event was `structuredClone`d three times before truncation, so multi-megabyte tool
  arguments and responses were copied for a payload that keeps 1200 characters of each.
- Payload enrichment read the session row whenever `workdir` was falsy, so a session with no project
  directory paid a database read per event.
- The `messageId` to `promptPreview` fallback read a message row and described the assistant's own
  output as the user's prompt, because every producer passes an assistant message id.
- Events were dispatched through independent microtasks with per-event asynchronous enrichment, so
  the order in which hook commands started did not match the order in which events happened.
- Three nested layers repeated the same defensive catch with two different loggers, while
  `dispatchEvent` remained a public untyped bypass of the runtime sink.
- `observeTerminal` resolved the project directory as an argument expression outside the sink's
  guard, so an envelope failure could propagate into run settlement.

### Event Contract

`HookEvent` is a discriminated union of one session envelope and one event body. Each variant is
closed against every fact it does not declare, so tool facts cannot ride on `Stop` and `PreToolUse`
cannot omit them. Closing the variants matters because excess property checking alone only refuses
surplus fields on a fresh literal: a variable carrying both `stop` and `tool` would otherwise
satisfy the union. Two compile-time guards keep it honest: one fails when the union and
`HOOK_EVENT_NAMES` drift apart in either direction, and one pins the combinations the union must
refuse, for both missing and foreign facts.

`HookEventName`, the settings shape, and the `payloadVersion: 1` wire payload are unchanged, because
they are the renderer contract and the user script contract respectively.

### Runtime Scope

`RuntimeHookSink` becomes a factory for `RuntimeHookScope`, a bound emitter that resolves one session
envelope once for the producer that created it: session and message identity, provider, model,
agent, and working directory. The scope holds no service graph, persists nothing, and duplicates no
runtime state. Producers emit event bodies against it, so no emit site assembles an envelope
field by field.

The scope is per producer, not per turn. A normal turn creates three: `TurnCoordinator` for the
submitted prompt, `DeepChatLoopRunner` for the run and its tool facts, and
`RuntimeHookSink.observeTerminal` for settlement, which reaches the sink from
`RunLifecycleCoordinator` without the turn's scope. Threading one scope through the run lifecycle
would add a new state carrier to that owner and is out of this slice, so a setting changed mid-turn
can still be observed by later events of the same turn.

The scope resolves the working directory lazily and once. An unobserved event resolves nothing, and
a resolution failure leaves the directory unanswered rather than dropping the notification, so
delivery can still resolve it from the session row.

`RuntimeHookScope.terminal` is the single terminal projection. Every settled turn, whatever its entry
point, reports `Stop` then `SessionEnd` through it.

The loop keeps its own tool-fact vocabulary. `DeepChatLoopNotification` describes what a tool batch
did, not what a hook consumes, and the scope's tool observer is the one adapter between them. Making
the loop import the hook contract to remove a structural duplicate would trade a real layer boundary
for cosmetic reuse.

### Delivery Semantics

`HookService` owns its configuration as state instead of re-reading a store per event. The
configuration and the derived subscription index are rebuilt as one unit, so a subscription probe
can never disagree with the hooks the delivery path will run. Every write path refreshes it
atomically: the routes write through the service, and `start()` covers the maintenance window that
already brackets configuration import.

`isObserved` is a synchronous set lookup exposed to producers. An unsubscribed event costs one
lookup: no envelope resolution, no projection, no clone, no database read, and no spawn.

An observed event is projected synchronously into detached wire facts, with previews truncated
first, so no oversized string reaches a clone. The permission record is the only field of unbounded
shape and receives the single bounded clone.

Delivery is serialized per session. Events of one session reach their commands in emission order,
and unrelated sessions never wait for each other. The guarantee is event acceptance and command start
order; completion order of external processes is not ordered, no hook is awaited, and Agent tool
execution never blocks on one.

Configuration is owned at acceptance, not at delivery. An accepted event carries the identity and
command of every hook eligible at that moment, and delivery runs a hook only when it is still
eligible under the same command. A hook enabled or edited after an event happened therefore never
receives it, and one disabled while the event was queued stops receiving it. Because enrichment is
asynchronous, that revalidation is settled immediately before spawning rather than when the delivery
was dequeued.

A hook command that outlives its timeout settles its own result. A killed shell can keep a process
tree alive and never emit `close`, so waiting for exit could leave the settings test call pending
forever. Captured stdout and stderr are bounded by the diagnostic limit they are truncated to, so a
command that streams for its whole timeout window cannot grow main-process memory without bound.

Fault isolation stays layered rather than collapsed. The loop guards its notification boundary, the
scope guards the runtime boundary so no hook failure reaches settlement, the service guards each
queued delivery, and each hook command is isolated from its siblings and from the next event.

### Included Corrections

| Correction | Previous behavior | Required behavior and rationale |
| --- | --- | --- |
| Terminal drift | Four independent `Stop` plus `SessionEnd` implementations computed their own reason and fallbacks, and each could drift again. | One projection owns the pair and normalizes a missing `usage` or `error` to an explicit null. ACP still reports `usage: null` because `AcpObserverPort.terminal` carries no token accounting; giving ACP real usage needs protocol design and is not part of this slice. |
| Assistant text as prompt preview | An event carrying a message id but no preview read that message and reported its text as `user.promptPreview`. Every producer passes an assistant message id, so the fallback was wrong at every call site and cost a row read per event. | Prompt previews come from the producer that owns the prompt. The message lookup and its query port are removed; `user.messageId` is unchanged. |
| Answered null project directory | Enrichment triggered whenever `workdir` was falsy, so an explicit null cost a session read that returned null. | Only an unanswered field triggers a lookup. |
| Unguarded envelope resolution | `observeTerminal` resolved the project directory outside the sink's guard, so a stale-instance assertion could propagate into run settlement. | Envelope resolution happens inside the guarded region and degrades to an unanswered field. |

`user.promptPreview` therefore becomes empty for tool events and for a resumed `SessionStart`, where
it previously contained assistant output. The payload shape, `payloadVersion`, environment variables,
command placeholders, command timeout, redaction, and settings contract are unchanged. `time` is now
captured when the event occurs rather than when its payload is assembled.

### Deferred Findings

| Finding | Observation | Current handling |
| --- | --- | --- |
| Hydrating project-directory read | `resolveProjectDir` reaches `getOrHydrateScope`, so resolving a hook envelope can recreate a runtime instance for an evicted session. | Pre-existing. The subscription gate removes the call entirely for unconfigured installations; making the read non-hydrating changes `SessionSettingsCoordinator` and belongs to its own change. |
| Hook command environment inheritance | Hook commands spawn with the full parent environment, so any secret in the Agent process environment is visible to them. | Pre-existing and out of scope for a delivery contract. Closing it requires an allowlist design and a settings surface. |
| Orphaned process trees | A timed-out command is killed with `SIGKILL` on the shell only, so grandchildren can survive and keep the child registered until the process exits. | The result no longer depends on that exit. Killing the whole tree and bounding concurrent hook processes is separate reliability work. |
| Per-turn envelope resolution | Three producers each resolve their own envelope, so a setting changed mid-turn can be observed by later events of the same turn. | Accepted. Threading one scope through the run lifecycle adds a state carrier to `RunLifecycleCoordinator` and belongs to its own change. |

## Typed Hook Notification Pipeline Goals

- Make the event name and its payload one value that cannot describe an impossible event.
- Give the terminal projection, and every session envelope, exactly one implementation.
- Make an unconfigured installation pay one map lookup per event.
- Make delivery order within a session deterministic without coupling sessions or blocking the Agent.
- Keep the user-facing hook contract, including the version 1 payload, byte-compatible.

## Typed Tool Execution Contract Goals (Completed)

- Define one canonical, typed execution contract for every `MCPToolDefinition`.
- Make a tool's maximum side effect and concurrency permission explicit at its definition site.
- Make `write + parallel` unrepresentable in TypeScript.
- Admit external MCP tools with a conservative contract without trusting server-supplied hints.
- Keep current runtime behavior: only a multi-call batch composed entirely of explicitly parallel
  read tools may run concurrently, and only in `full_access` mode.
- Preserve tool-call result ordering, failure isolation, abort behavior, permission recovery,
  interaction pauses, output fitting, and durable execution-state updates.
- Keep execution-only metadata out of provider payloads, context-window token estimates, and
  provider-view tool-definition hashes.

## Execution Contract

The canonical definition uses a closed discriminated union:

```ts
type ToolExecutionMode = 'sequential' | 'parallel'
type ToolEffect = 'read' | 'write'

type ToolExecutionContract =
  | { effect: 'read'; mode: ToolExecutionMode }
  | { effect: 'write'; mode: 'sequential' }

type MCPToolDefinition = MCPToolDefinitionBase & {
  execution: ToolExecutionContract
}
```

`effect` describes the maximum observable capability of the tool, not the behavior of one selected
argument branch:

- `read` means the tool has no durable, external, user-interaction, or non-idempotent mutation that
  can conflict with another call.
- `write` includes any tool that can mutate state, incur an externally visible action, pause for an
  interaction, or select a mutating operation based on arguments.

`execution.mode` is a positive scheduling grant. A `read` tool may remain sequential because of
resource limits, internal mutable caches, ordering requirements, or because concurrency has not yet
been validated. Only an explicit `read + parallel` contract authorizes parallel execution.

A single deeply frozen `TOOL_EXECUTION` preset catalog represents the three valid contracts.
Definition sites assign one atomic `execution` value, for example `TOOL_EXECUTION.write` or
`TOOL_EXECUTION.read.parallel`, instead of spreading independent fields into the definition. Runtime
freezing prevents one definition from mutating the shared preset used by every other definition.
This keeps execution-only metadata namespaced, makes the classification concise and reviewable, and
lets provider-facing projections remove the complete contract when it evolves. `MCPToolDefinition`
has one canonical declaration under `src/shared/types/core`; the broader shared MCP module aliases
that declaration instead of maintaining a second structural copy.

## Trust And Classification

Built-in Agent definitions declare their contract at the owning definition site. For compatibility
with current behavior, only the filesystem `read` tool is initially `read + parallel`.

- Filesystem `glob` and `grep`, Tape queries, `skill_list`, and `get_browser_status` are
  `read + sequential` until their concurrency behavior is deliberately validated.
- File mutation, commands, processes, questions, plans, settings, image generation, cron jobs,
  subagents, skill activation/management/execution, and browser navigation/CDP are
  `write + sequential`.
- All memory tools are `write + sequential`; recall updates access metadata and is not a pure read.
- Parameter-dependent tools use their maximum capability. A tool that can either inspect or mutate
  is classified as `write`.

External MCP and plugin tools enter the catalog as `write + sequential`. MCP annotations such as
`readOnlyHint` are descriptive hints from an untrusted server and never grant local concurrency.
Supporting trusted per-server overrides would require a separate authenticated policy design.

## Batch Scheduling

A pure runtime policy selects `parallel` only when every condition holds:

1. the Session permission mode is `full_access`;
2. the batch contains at least two calls;
3. every call resolves to exactly one definition;
4. every resolved definition is `read + parallel`.

Missing, malformed, or duplicate definitions fail closed to `sequential`. Mixed batches remain
fully sequential; this change does not introduce segmented scheduling around write calls. Parallel
execution continues to settle independently and commit results in provider call order.

## Compatibility

- The runtime tool-definition shape gains one additive internal `execution` object, so structural
  readers and IPC consumers remain compatible. The contract is intentionally required in
  TypeScript, which is a source-level contract tightening for definition constructors; all
  in-repository constructors are updated atomically. No persisted format or database migration
  changes.
- Provider adapters continue projecting only model-visible function metadata. Legacy XML prompt
  generation also continues reading only function metadata.
- Context reserve estimation excludes the execution object so this internal contract does not
  reduce user-visible context capacity.
- Tape ViewManifest tool-definition hashes exclude the execution object, preserving their existing
  provider-view identity. Future execution-policy replay must use a separate versioned identity.
- Existing external MCP tools remain sequential. Existing built-in behavior remains unchanged:
  only Agent filesystem `read` batches gain the parallel path they already had.
- Permission preflight, auto-grant, post-call permission handling, and interaction semantics remain
  owned by dispatch.

## Non-Goals

- Do not parallelize additional built-in or MCP tools in this slice.
- Do not infer execution policy from MCP annotations, names, schemas, permission results, or call
  arguments.
- Do not add per-call dynamic effect classification or a segmented dependency scheduler.
- Do not rewrite tool dispatch or change durable queue and Tape semantics.
- Do not add the typed hook reducer in the harness facade slice.
- Do not change provider retry, tool scheduling, dispatch internals, public IPC/API contracts,
  persisted formats, Tape semantics, or Memory semantics beyond the corrections explicitly listed
  above while extracting runtime owners.
- Do not add a Pi-style `run`/`steer`/`followUp`/`subscribe` vocabulary or any third event channel;
  the facade implements the existing manager and session contracts only.
- Do not pass the composed service graph to hooks as a container. `RuntimeHookScope` carries
  immutable session facts only.
- Do not give hooks a decision channel. Blocking, replacing, cancelling, or rewriting provider
  requests and tool arguments would require a versioned stdout protocol, a conflict and timeout
  policy, argument revalidation, permission recheck after rewriting, and a trust surface. It is a
  product feature with its own security model, not part of a delivery contract, and DeepChat has no
  in-process participant that could use it today.
- Do not fix defects discovered while moving code beyond the narrow final-review corrections listed
  above; record them and open a separate branch.
- Do not move durable pending inputs into `DeepChatAgentInstance` or add another source of truth.
- Do not make `SessionRuntimeScope` a dependency container or introduce a generic runtime kernel.
- Do not add same-run steering or change abort semantics, queue ordering, or visible-turn behavior
  beyond rollback of a rejected claimed live send described above.
- Do not create or synchronize a GitHub issue for this architecture work.

## Typed Tool Execution Acceptance Criteria (Completed)

1. The canonical type rejects `write + parallel`, and all production definition ingress paths
   produce an explicit valid contract.
2. Two or more `read + parallel` calls run concurrently only in `full_access`, while result commits
   preserve call order.
3. A sequential read, write, missing definition, malformed contract, duplicate definition, or
   non-`full_access` permission mode makes the complete batch sequential.
4. External MCP definitions remain `write + sequential` even when `readOnlyHint` is true.
5. Parallel failures remain isolated per call; abort and already-returned-result settlement retain
   current behavior.
6. Execution metadata is absent from provider tool schemas and does not change the historical tool
   token reserve or ViewManifest tool-definition hash.
7. Focused tests, type checks, formatting, i18n validation, and lint pass before handoff.

## Coordinator Ownership Acceptance Criteria

1. Tests no longer reflect coordinator private members that move in this slice; owner tests use
   public behavior or typed ports.
2. `DeepChatAgentRuntime` is the only hydrated-instance registry, and the minimal scope exposes no
   project, settings, hook, store, or provider service access.
3. Instance, active-run, operation-controller, and message-association fences remain explicit and
   retain their existing semantics.
4. Status transitions preserve their current four-sink ordering and stale instances/runs cannot
   overwrite a replacement's status.
5. One owner performs terminal settlement and active-run cleanup for initial, resume, interaction,
   cancel, and provider-return paths without duplicate queue wakeups.
6. `PendingInputPump` is the only queue-drain and claim-state owner. Steer priority, blocked-input
   gates, restart recovery, and per-session single flight remain compatible; retry rollback and the
   twelve enumerated corrections follow their explicitly tested semantics.
7. Internal turn completion maps losslessly to the existing `MessageStartResult`, including OCR and
   attachment-preparation outcomes.
8. Manual and automatic compaction retain current abort, stale-instance, status, transcript, Tape,
   and Memory ordering through the existing compaction owner.
9. `DeepChatRuntimeCoordinator` contains composition and public compatibility delegation rather
   than lifecycle, admission, pump, settlement, or compaction implementations. Owner modules do not
   import the concrete root coordinator.
10. Focused owner tests, the full main-process suite, type checks, formatting, i18n validation,
    lint, and architecture guards pass before handoff.

## Harness Facade Acceptance Criteria

1. `deepChatRuntimeCoordinator.ts` no longer exists. Identity, state resolution, session lifecycle,
   transcript mutation, and message projection each have one named owner, and the prompt assembler,
   tool result normalization, and permission review remain domain modules bound at composition.
2. No module under `src/main/agent/deepchat/{runtime,loop,instance,memory,resources}` imports the
   harness layer, enforced by the agent cleanup guard.
3. The composition root is the only module that wires owners, and the graph contains exactly one
   named late binding, from `RunLifecycleCoordinator` to `PendingInputPump`.
4. `DeepChatAgentInstance` exposes runtime state only; `DeepChatAgentInstanceDelegate`, the registry
   hydrator, and `DeepChatAgentRuntime.dispose()` are gone, and the manager backend reaches send,
   cancel, snapshot, and close through the harness port.
5. The facade declares no field other than its owners and the bound ACP compatibility dependency
   factory, and satisfies `DeepChatAgentBackendPort`, `SessionStatePort`, and
   `SessionTranscriptRuntimePort` by explicit `implements` clauses.
6. `DeepChatLoopRunnerPorts`, `TurnCoordinatorPorts`, and `InteractionCoordinatorPorts` shrink, the
   six Tape capabilities are one composed port, and no port member is a closure over the facade.
7. Every owner introduced or narrowed in this slice is constructible in a test without building the
   full runtime, and each has a focused suite. Compacting the retained full-runtime suite is
   deferred with its reason recorded in the test boundary.
8. The four load-bearing sequences in the zero-behavior-change constraint are pinned by tests that
   fail if their order changes.
9. No source behavior, public contract, persisted format, event payload, or event ordering changes,
   apart from the build preflight resource refresh and final-review corrections recorded above.
   Executed test count does not drop.
10. The harness barrel exports only the factory entry point and its types, deep imports into the
    harness directory are rejected from outside it, and both rules fail closed in the guard.
11. The runtime source graph is acyclic across the owners this slice touched: no owner pair and no
    harness module pair import each other, including type-only imports.
12. Focused owner suites, the full main-process suite, type checks, formatting, i18n validation,
    lint, the agent cleanup guard, and a regenerated layered-runtime baseline pass before handoff.

## Typed Hook Notification Pipeline Acceptance Criteria

1. `HookEvent` correlates every event name with its payload, refuses a foreign fact through a
   variable as well as a literal, and the coverage and rejection guards fail compilation when a name
   loses its body variant or a required fact becomes optional.
2. No emit site assembles a session envelope field by field, and `Stop` plus `SessionEnd` has one
   implementation that all four previous entry points reach.
3. An event with no enabled subscriber performs one subscription lookup and nothing else: no
   envelope resolution, projection, clone, settings read, session read, or spawn.
4. A configuration write through the service refreshes the subscription index atomically, and a
   write that bypasses the service is picked up by the next explicit refresh or service restart.
   A hook enabled or edited after an event was accepted never receives that event, and a hook
   disabled while the event was queued does not run it.
5. Tool previews are truncated before any clone, and the permission record receives the only clone.
6. Events of one session start their commands in emission order under delayed enrichment, and a
   stalled session does not delay another.
7. A hook command that fails to spawn, times out, or throws does not stall its siblings or the next
   event, and no hook failure reaches run settlement. A timed-out command settles its own result
   without waiting for an exit that may never arrive, and captured diagnostics stay bounded.
8. `payloadVersion`, the stdin payload shape, environment variables, command placeholders, the
   command timeout, and the settings contract are unchanged; `user.promptPreview` reports only a
   prompt the producer supplied.
9. Focused suites, the full main-process suite, type checks, formatting, i18n validation, lint, and
   the agent cleanup guard pass, and executed test count does not drop.

---

### Architecture Deepchat Agent Harness Boundaries Tasks (`docs/architecture/deepchat-agent-harness-boundaries/tasks.md`)

# DeepChat Agent Harness Boundaries Tasks

## Typed Tool Execution Contract

- [x] Compare Pi and Bub execution semantics with DeepChat's current runtime.
- [x] Record the 156-test dispatch, context-builder, and ToolService baseline.
- [x] Define the trust boundary, capability model, scheduling invariants, and non-goals.
- [x] Review and commit the SDD slice.
- [x] Add the canonical nested execution contract and shared preset catalog.
- [x] Classify every built-in definition and default external MCP definitions conservatively.
- [x] Extract and integrate the fail-closed batch execution policy.
- [x] Preserve provider projection and historical context-reserve behavior.
- [x] Add policy, dispatch, MCP ingress, provider-boundary, and token-boundary coverage.
- [x] Run focused tests and full type checking.
- [x] Run formatting, i18n validation, lint, and the full main-process suite.
- [x] Review the complete implementation diff and fix every finding.
- [x] Commit the implementation without pushing.

## Coordinator Ownership

- [x] Compare Pi and Bub orchestration boundaries with DeepChat's durable runtime semantics.
- [x] Define minimal Session scope and the four distinct lifecycle fence semantics.
- [x] Define owner responsibilities, typed claim disposition, feedback-loop wiring, compatibility,
      and non-goals.
- [x] Remove private coordinator reflection from runtime tests without changing production source.
- [x] Add status-order, settlement, fencing, queue-disposition, return-mapping, and Memory baselines.
- [x] Add minimal runtime scopes and extract status/pre-stream/context-budget policies.
- [x] Make one coordinator own run lifecycle, cancellation, settlement, and queue wakeup.
- [x] Extract pending-input admission and the single queue pump; delete duplicate claim/drain logic.
- [x] Extend the existing compaction owner and reduce the root to composition and compatibility.
- [x] Split owner tests while retaining focused full-runtime integration coverage.
- [x] Update architecture guards and regenerate the layered-runtime baseline.
- [x] Correct the SDD to enumerate all twelve intentional ownership behavior changes.
- [x] Pin the follow-up/drain overlap and verify the four existing ownership regression tests.
- [x] Treat concurrent terminal interaction settlement as clean ownership loss.
- [x] Tighten claim settlement typing and preserve primary settlement errors.
- [x] Cache runtime scopes and share transcript parsing across pending-input gates.
- [x] Replace regex ownership checks with fail-closed TypeScript AST checks.
- [x] Correct claim diagnostics and stale-instance error classification.
- [x] Make pending-input single flight atomic and token-owned before the first asynchronous read.
- [x] Prevent cleanup hydration and terminalize every canceled interaction message.
- [x] Stop terminal persistence from referencing messages removed by retry rollback.
- [x] Standardize recovery logging and remove duplicated lane/project-directory mutation paths.
- [x] Add focused admission, drain-race, cancellation, and rollback regression coverage.
- [x] Run pull-request review validation and inspect the complete follow-up diff.
- [x] Commit the reviewed pull-request follow-up without pushing.
- [x] Run focused and full validation, review the complete diff, and fix every finding.
- [x] Commit every reviewed stage without pushing.

## Harness Facade

- [x] Specify the owner map, port narrowing rules, single late binding, and zero-behavior-change
      invariants in this architecture record.
- [x] Extract identity, state resolution, session lifecycle, transcript mutation, and message
      projection owners; make `resolveStreamRequestId` a pure helper.
- [x] Bind the prompt assembler factory, tool result normalization, and permission review through
      named ports without wrapping domain functions in single-method classes.
- [x] Replace registry-shaped callbacks with `SessionScopeRegistry` and `SessionRuntimeScope`.
- [x] Replace remaining owner callbacks with concrete collaborators and compose the six Tape
      capabilities into one domain port.
- [x] Introduce the named pending-input wakeup binding and remove every other deferred wiring.
- [x] Delete `DeepChatAgentInstanceDelegate`, the registry hydrator, and `dispose()`; route manager
      backend send, cancel, snapshot, and close through the harness port.
- [x] Add the composition factory and the facade implementing the existing manager and session
      contracts.
- [x] Delete `deepChatRuntimeCoordinator.ts` and migrate app composition, ACP compatibility, session
      deletion, and transcript mutation wiring.
- [x] Pin state hydration, message refresh, status publication, and destroy ordering with tests that
      fail if their order changes.
- [x] Add focused owner suites for every extracted owner without reducing executed test count.
- [x] Keep the harness barrel package-private around the owner graph, its factory, and the wakeup
      binding, and guard the export surface plus deep imports.
- [x] Break the interaction/turn and facade/factory source cycles through neutral contract modules.
- [x] Record the build preflight resource refresh as the one exception to the zero-behavior-change
      constraint.
- [ ] Make the retained full-runtime suite compact by moving its owner-specific describe blocks into
      the owner suites. Deferred: the suite declares four module-scope `vi.mock` factories that
      cannot be shared across files without restructuring the mock wiring.
- [x] Replace root guard rules with harness boundary rules and a smaller facade size ceiling.
- [x] Regenerate the layered-runtime baseline and update affected architecture records.
- [x] Fence asynchronous Session settings updates to one runtime scope and add replacement-race
      coverage.
- [x] Close harness barrel export escapes and require explicit pending-input rollback ownership.
- [x] Run focused and full validation, review the complete diff, and disposition every finding.
- [x] Commit every reviewed stage without pushing.

## Typed Hook Notification Pipeline

- [x] Record the 16-test hook baseline and enumerate the contract, ordering, and cost defects.
- [x] Specify the event contract, runtime scope, delivery semantics, corrections, and non-goals.
- [x] Add the canonical `HookEvent` union with coverage and rejection guards.
- [x] Replace the untyped observer contract with `isObserved` plus a typed `notify`.
- [x] Make `HookService` own its configuration and derived subscription index with atomic refresh.
- [x] Project and truncate synchronously, then clone only the permission record.
- [x] Serialize delivery per session without coupling sessions or awaiting hook commands.
- [x] Turn `RuntimeHookSink` into a scope factory and unify the terminal projection.
- [x] Migrate the loop, turn, interaction, and ACP compatibility producers onto the scope.
- [x] Route hook configuration writes through the service and drop the unused message query port.
- [x] Add subscription, ordering, projection, terminal parity, and resilience coverage.
- [x] Run focused and full validation, review the complete diff, and disposition every finding.
- [x] Commit every reviewed stage without pushing.

## Future Pull Requests

- [ ] Design hook decision semantics separately if the product and security model are approved.
- [ ] Design same-run steering separately if the product semantics are approved.

---

### Architecture Durable Execution Journal Plan (`docs/architecture/durable-execution-journal/plan.md`)

# Durable Execution Journal Implementation Plan

## Architecture

The implementation adds a pure Execution Journal domain model, a strict Tape application service,
and narrow runtime capabilities. Context Tape and Execution Journal share the physical
`deepchat_tape_entries` table but have different persistence contracts.

```text
DeepChat loop / deferred executor
  -> ExecutionJournalWriter (strict, synchronous)
  -> TapeEntryStore transaction
  -> immutable execution/* event

Tool execution
  -> per-call commitDispatch callback
  -> final resolved Agent/MCP boundary
  -> target invocation

Startup
  -> ExecutionJournalRecoveryReader
  -> pure classifier
  -> diagnostic report with parked disposition
  -> existing interrupted-message projection
```

The application service owns identity-to-provenance derivation, canonical payload construction,
strict idempotency comparison, parsing, and classification. Generic Context Tape append methods
reject the reserved `execution/*` namespace; the strict writer uses a dedicated storage method that
accepts only the four protocol event names. That method is exposed through a dedicated Journal
persistence port and is absent from the generic `TapeEntryStore` port. Fork merge excludes the
entire reserved namespace.

`SessionTape` is process-lived while the Session SQLite connection can be closed and reopened by
import, encryption, and maintenance flows. The Journal service therefore receives a lazy store
provider and resolves it for every commit and recovery scan, matching the existing Context Tape
provider pattern. No store object backed by a concrete SQLite handle is cached across reopen.

## Domain Model

Create `src/main/tape/domain/executionJournal.ts` with:

- protocol and event-name constants;
- Run and operation identity types and validators;
- typed inputs and durable payload types for the four fact families;
- canonical operation identity hashing;
- row parsers that reject malformed or unsupported native facts;
- a pure recovery classifier and report types;
- typed persistence and corruption errors.

Operation provenance keys use a stable prefix plus a SHA-256 hash of the canonical identity tuple.
Run facts use the UUID run identity directly in their source and a hashed provenance key. Event
payloads repeat structured identity fields so rows remain self-describing and independently
auditable.

## Strict Writer

Create an Execution Journal application service over the existing Tape providers. Each commit:

1. validates and canonicalizes the typed input;
2. builds the exact expected event row fields and payload;
3. enters the existing synchronous SQLite transaction;
4. reads by provenance key;
5. returns `{ created: false }` only when every identity-bearing and payload field is canonically
   identical;
6. throws a typed corruption error when the key exists with different fields;
7. otherwise appends a non-idempotent event and returns `{ created: true }`.

An append/storage failure is wrapped with fact identity context and propagated. The service does not
log-and-continue. Canonical hashing and strict row equality use the same null-prototype JSON
implementation, including rejection of non-finite numbers, sparse arrays, accessors, `undefined`,
and symbol keys.

The writer verifies that no host transaction is active before beginning its own transaction. This
prevents an outer rollback from erasing a receipt after the execution path has treated it as durable.
Callers perform local read/validation preflight, commit T1, then enter their synchronous mutation
transaction without an intervening asynchronous boundary.

Extend the Tape capability ports with:

- `ExecutionJournalWriter` for Run and operation commits;
- `ExecutionJournalRecoveryReader` for startup classification;
- `DeepChatLoopTapePort` composition of the writer capability.

`SessionTape` delegates these methods to the new application service. No tool handler receives the
facade or raw store.

## Storage Query

Add a dedicated Journal-store query and covering event index. SQLite first identifies native
`run_started` rows that have no matching `run_terminal`, then returns only the four v1 fact names for
those Runs in deterministic order. Historical terminal Runs, Context Tape rows, and their payloads
never cross the storage port during synchronous startup. Recovery parsing treats malformed facts in
the selected Runs as corruption instead of skipping them.

The anti-join still examines the compact Run-identity index, but startup payload parsing and memory
are proportional only to unterminated Runs. No mutable operations or open-Run table is introduced.
If measurements later require constant-time enumeration, that state must be a rebuildable projection
of Tape facts rather than a second authority.

## Run Identity And Lifecycle

Replace the loop runner's process-local sequence ID with `crypto.randomUUID()`.

Normal loop ordering becomes:

```text
local Run construction and preflight
  -> commit run_started
  -> register Run
  -> execute provider/tool loop
  -> commit run_terminal
  -> transcript/status/hooks/UI terminal projection
```

`processStream` receives the narrow writer through its I/O collaborators. Terminal settlement
commits exactly one of `completed`, `paused`, `aborted`, or `error` before calling the current
finalizers. If a post-terminal projection throws, the committed terminal fact is retained and no
conflicting terminal fact is attempted. Committed error and aborted fallback terminals are projected
through their matching finalizers, and the committed outcome takes precedence over the shape of the
projection error. Completed or paused terminal projection failures only clear transient runtime
state because the outer coordinator cannot reconstruct their full output.
If `run_started` or `run_terminal` persistence itself fails, the coordinator releases queued claims
and clears transient status without fabricating transcript/UI terminal evidence, then propagates the
Journal error. When Run execution and its fallback terminal commit both fail, a fresh typed error
retains both causes and preserves a concrete corruption classification without mutating either
captured error object.
Steer claims are the exception to release: their user messages are already durable and visible, so a
Journal failure consumes the claim to prevent duplicate replay. Cancellation classification is based
on the owned abort signal, never only on an exception name.

Deferred execution distinguishes a committed error terminal from a terminal commit failure. A
committed error terminal follows the normal error finalizer. If the terminal itself did not commit,
the interaction coordinator does not write terminal metadata/status or publish terminal events. A
pre-T1 failure leaves the approval available for an explicit retry. A post-T1 failure parks all
interactions on the message, projects only a committed T2 result or an indeterminate diagnostic into
the still-pending message, and keeps a replay guard across runtime cleanup and rehydration until the
Session is deleted.

## Tool Dispatch And Outcome Flow

Define the harness-internal execution options as `ToolCallOptions` with a required per-call
`commitDispatch` callback carrying:

- source (`agent` or `mcp`);
- normalized arguments;
- resolved tool name;
- resolved target metadata.

The loop creates the callback from the current operation identity. It marks the operation as
dispatched only after a newly created T1 receipt. An identical existing claim throws before target
invocation so an in-process retry cannot repeat the effect.

Only the internal `ToolExecutionPort.execute` contract requires this capability. Lower-level tool
services retain an optional callback because host-initiated reads and other non-Journal execution
paths remain valid. The adapter therefore forwards the typed options without a redundant runtime
presence gate.

MCP invokes the callback after argument preparation and the final policy, server, binding, client,
target, and abort checks, immediately before `targetClient.callTool`.

Agent handlers invoke the callback after their local schema, permission, path, target, session,
availability, duplicate, tombstone, and no-op checks, immediately before a persistent mutation,
process spawn, provider call, browser call, scheduler mutation, or delegation mutation. Pure reads
and local interaction tools do not claim a dispatch. When a lower service owns the final decision,
the handler forwards a once-only boundary and that service invokes it at the first actual effect.
For a local target sharing the Tape SQLite connection, T1 is committed outside the host transaction
after read preflight and immediately before the synchronous mutation transaction begins.

Handlers with a bounded internal fallback keep one operation identity in v1, but the dispatch hash
must cover the complete resolved invocation plan before the first target call. Shell
execution records the unchanged approved command and does not replay it after capability errors.
Process handlers validate the final spawn cwd before committing dispatch. A permission response
after dispatch is a protocol violation: commit it as a known error outcome, then fail the Run closed
instead of retrying or projecting it as an ordinary permission pause.

After execution, the loop normalizes and prepares the result. If T1 was committed, it commits T2
before the harness applies the finalized result to conversation messages, assistant blocks,
transcript, or UI. Live progress, runtime activation, and other events emitted inside an Agent tool
belong to the target invocation itself and can occur between T1 and T2; a crash in that interval is
classified from the resulting T1-without-T2 evidence.
The strict writer hashes the prepared response and persists only that hash and the error bit.
Response text and temporary offload paths remain with the projection consumer and are never persisted
as Journal facts.
Harness-owned skill activation occurs after T2. Activation failure is a projection/context failure
and cannot rewrite a successful target response as an error outcome. It still stops the current Run
so the next provider request cannot proceed with an outcome that claims activation while runtime
context lacks the activated Skill.
Thrown target errors are known error outcomes and receive T2. Abort or process loss before a known
result intentionally leaves T1 without T2. Permission retries clear prior attempt results before the
approved attempt begins, while a target result that has already returned is retained if cancellation
arrives during local result preparation.

Pre-dispatch `invalid_fact` errors caused by one model-controlled call are converted into that call's
refusal and do not prevent unrelated calls in the same batch. Journal persistence, corruption,
duplicate dispatch, and any post-dispatch Journal error bypass normal tool-error conversion and fail
the Run closed. They also take precedence over concurrent cancellation when parallel tool outcomes
are collected. A committed outcome receipt must be newly created; an existing receipt is treated as
a protocol violation before projection.

Parallel batches do not gain atomic result delivery in v1. If one sibling fails closed after other
siblings committed T2, their staged results can remain undelivered. The journal preserves those
known outcomes, but replaying them or exposing a durable delivered/not-delivered state requires a
separate batch-delivery contract.

## Deferred Execution

Approved deferred execution performs local tool/session resolution first, then creates a fresh UUID
Run with request sequence `1`. It commits `run_started`, passes the same real-boundary T1 callback,
commits a prepared T2 when dispatched, and commits a terminal fact before returning the result to
the interaction coordinator for transcript projection.

Permission responses that occur before dispatch terminate the physical deferred Run as `paused`
without fabricating T1 or T2. An ordinary pre-dispatch execution failure terminates it as `error`.
A later approval starts another physical Run.

## Startup Recovery

Harness construction reads and classifies journal facts before wiring the runtime graph and before
pending transcript messages are recovered. Classification is deterministic and never calls a tool:

- `not_dispatched`: native Run start with no dispatch;
- `completed`: every native dispatch has exactly one matching native outcome;
- `indeterminate`: at least one dispatch has no outcome;
- `corruption`: malformed, unsupported, conflicting, or orphaned facts.

Only native v1 journal event names are inputs. Transcript rows, legacy backfill facts, tool block
facts, provider attempt events, and model text cannot raise the evidence level.

The first version logs at most 100 bounded, control-character-sanitized candidate details plus a
classification summary and retains the existing interrupted-message projection. Corruption details
are ordered before indeterminate and other incomplete Runs so the display cap cannot hide the most
severe evidence. `indeterminate` and `corruption` remain parked; no replay or retry path is added.

Startup classification also returns Session-scoped message identities whose incomplete Runs are
`completed`, `indeterminate`, or `corruption`. Pending transcript recovery force-recovers those
messages even when they still contain an actionable interaction, closing the restart replay path
after a deferred parking projection could not be persisted. `not_dispatched` messages retain their
resumable interaction because no T1 exists.

The v1 recovery query uses an indexed anti-join over Run identities and loads fact payloads only for
Runs with a start and no terminal. Classification memory is proportional to those Runs and their
operations, not total Journal history or persisted response size. V1 has no durable acknowledgement
or retention state, so an unterminated Run can produce the same bounded startup diagnostic on later
launches.

Synchronous startup is not a permanent global corruption audit. Malformed facts belonging to a
selected unterminated Run remain fail-closed, while corruption hidden behind a terminal Run requires
an explicit offline/background audit. A later audit, acknowledgement, archive, or compaction design
must remain derivable from immutable facts; an arbitrary row limit is not an acceptable substitute.

Recovery normalizes inherited non-interaction `pending` or `loading` blocks to errors before a new
Run begins. Pending permission and question blocks remain resumable. A current Run still fails loudly
if it attempts to project a paused terminal with unresolved non-interaction work.

## Transcript Order Corrections

Compaction can insert a status message at an existing transcript `orderSeq` and shift later rows by
one. The shift and a Context Tape replacement for every affected message run in the same SQLite
transaction before the compaction indicator is inserted. Shift replacements use provenance that
includes the resulting order so multiple shifts within one clock tick cannot collapse into one
idempotent row. Effective Tape and transcript ordering therefore remain identical after compaction,
resume, or steer insertion. A replacement for a compaction placeholder preserves its excluded
`message/compaction_indicator` event shape rather than creating a normal assistant message fact.

Replacement writers receive an explicit revision kind. Record revisions retain the existing
updated-at identity, while order revisions add the resulting `orderSeq`; neither provenance rule is
inferred from the reason text or correction metadata. Order-shift materialization fetches only the
captured message IDs in batches of at most 500. Each batch also bounds the normalized child-table
queries, and all batches execute synchronously inside the existing outer transaction.

An order-only replacement appends the corrected message or compaction-indicator fact but does not
rewrite its tool call/result facts. Effective views project tool `orderSeq` from the corresponding
effective message and use the persisted tool payload value only when no effective message can be
resolved. Existing payloads and provenance keys remain unchanged. When changed content returns to a
previous payload hash, the new fact keeps the legacy hash prefix and adds the effective entry it
supersedes so the append cannot resolve to the older revision.

Reconciliation builds a map of the current effective tool revision by logical tool identity. Its
semantic fingerprint includes kind, name, terminal status, and every persisted tool payload field
except `orderSeq`. A backfill candidate equal to that current content is skipped; a changed candidate
is appended and becomes the new effective revision. Comparing only with the current revision is
required so a legitimate A -> B -> A content change is not mistaken for an old duplicate.

Synthetic summary checkpoint contributions cite the reconstruction anchor entry only when that
anchor carries the same normalized summary. This keeps ViewManifest lineage auditable without
attributing a legacy or otherwise unrelated summary to the latest anchor.

## Completed Process Session Reconciliation

The main process retains completed utility-process session ownership so the owning conversation can
clear, kill, or remove a completed session without opening cross-conversation access. A single
unref'ed reconciliation timer queries each affected conversation at the utility host's cleanup
cadence and removes local completed entries only after the host confirms they are absent. Explicit
remove remains idempotent when host TTL cleanup wins the race, while transport and unrelated host
errors continue to propagate.

## Test Strategy

### Pure Unit Tests

- operation identity validation and collision-safe key derivation;
- parser rejection for malformed and unsupported facts;
- all four recovery classifications;
- outcomes without dispatch, facts without Run start, duplicate terminals, and session mismatch;
- response/argument payload hashing and bounded data rules.
- strict canonical JSON rejection and `__proto__` preservation in both write and equality paths.

### Native SQLite Tests

- identical strict commit is idempotent;
- conflicting strict commit throws and preserves the original row;
- dispatch commit failure propagates and prevents the supplied target callback from running;
- unterminated-Run query uses the intended index and excludes terminal Runs and Context Tape events;
- outcome facts retain a response hash without persisting response text or an offload path;
- restart-style reconstruction from persisted rows.
- reserved namespace guards, fork exclusion, and rejection of commits inside host transactions.
- bounded compaction-shift message materialization under the portable SQLite parameter limit.
- repeated order shifts followed by reconciliation without tool fact growth.

### Runtime Tests

- UUID Run creation and run-start-before-registration ordering;
- explicit record/order replacement modes and order-derived tool recall refs;
- tool content revisions remain append-only and supersede the prior effective revision;
- MCP validation/policy/target failures produce no T1;
- MCP T1 precedes the target call and duplicate T1 prevents a second call;
- representative Agent mutation boundaries produce the same ordering;
- T2 precedes harness-owned finalized result projection for success and known failure;
- T2 failure prevents projection;
- committed error and aborted outcomes select their matching finalizers even when projection throws;
- a parallel sibling failure leaves earlier T2 facts intact without claiming atomic batch delivery;
- terminal commit precedes every terminal projection and failure prevents projection;
- terminal fallback failure preserves the Run error, commit cause, and Journal corruption subtype;
- deferred approval uses a new Run and obeys the same ordering;
- startup classification runs before interrupted transcript recovery, loads only unterminated Runs,
  and never executes tools;
- internal tool execution requires its dispatch capability at compile time and forwards it unchanged;
- Run-start and terminal persistence failures release claims and clear transient status without
  fabricating terminal projections;
- deferred terminal-commit failure after T1 consumes and guards the approval without writing Run
  terminal metadata/status/events, and startup recovery cannot expose it for replay;
- cron, process, memory, and delegation no-op/refusal paths produce no T1, while their first actual
  side effect follows T1;
- completed process sessions retain owner metadata until explicit cleanup or confirmed utility-host
  expiry, while cross-conversation mutations remain rejected;
- completed process-session ownership is periodically reconciled with the utility host, and a
  cleanup request remains idempotent when the host already removed the completed session by TTL;
- closing and reopening the Session database does not invalidate later Journal commits;
- compaction insertion that shifts transcript order produces matching effective Tape order;
- visible steer claims are consumed when the next Run cannot commit `run_started`;
- concurrent cancellation cannot turn deferred Journal parking into an aborted terminal projection;
- projection failure followed by runtime cleanup cannot make a parked deferred tool replayable;
- target outcomes commit before skill activation, and activation failure cannot change T2;
- non-user `AbortError` exceptions remain errors unless the owned signal is aborted;
- inherited unresolved non-interaction blocks are normalized before resume/pause projection.

### Crash Tests

Add deterministic failpoints immediately inside/outside T1, T2, and terminal boundaries. A native
child-process test sends `SIGKILL`, reopens the same SQLite database, runs classification, and proves
the expected evidence state. Platform-gate the real signal test where necessary while keeping pure
and native SQLite coverage portable.

## Validation

Run the smallest relevant suites during implementation, then before handoff run:

```text
pnpm run format
pnpm run i18n
pnpm run lint
pnpm run typecheck
pnpm exec vitest run <relevant Tape, loop, tool, deferred, and harness suites>
```

Run native SQLite tests through the repository's documented native test command or environment
gate. Report any platform-gated crash test separately.

## Commit Strategy

1. Architecture SDD.
2. Execution Journal domain, strict Tape service, storage query, and focused tests.
3. UUID Run identity plus normal loop T1/T2/terminal integration and tests.
4. Agent/MCP resolved-boundary coverage and tests.
5. Deferred execution, startup classification, crash tests, and architecture documentation updates.

Before each commit, review unstaged and staged diffs for hidden side effects, compatibility,
boundary conditions, performance, security, naming, test gaps, and maintenance cost. Fix findings,
rerun relevant validation, then commit with a concrete Conventional Commit message. Do not push.

## Journal v2 Programmatic Extension Plan

1. Add discriminated provider/nested parsers and strict canonical identity/payload validation while
   preserving all v1 read and recovery behavior.
2. Add bounded contiguous plan-index child ordinals and parent references; reserve the complete
   process-live step/template mapping before child approval/T1 and prohibit caller-, materialization-,
   or completion-order assignment.
3. Add a process-live parent-operation controller and settlement receipts crossing local control;
   never infer settlement from stdout.
   Construct per-View capability before provider response, derive the exact invocation grant only
   after the outer call exists, and arm it only from a newly created parent T1 receipt.
4. Enforce parent T1 before child T1, child T2 before projection, complete child settlement before
   outer T2, and rejection of outer T2/`run_terminal` while any Programmatic T1 is unmatched.
5. Persist only hashes/status/bounded provenance and propagate every nested Journal failure as
   Run-fatal.
6. Cover known outcomes, denial, pending approval, cancellation, crash, T1-only parking, missing
   outer T2, explicit new-operation retry, duplicate T1, corruption, and v1/v2 coexistence.
   Include unarmed-token denial, parent-T1 failure before spawn, foreground-only CLI execution, and
   canonical outer-result receipt validation. Add failpoints proving terminal commit cannot hide an
   unmatched outer or nested T1 from recovery.
7. Perform a severity-ranked review before each commit and do not push.

---

--- METRICS ---
- Files Extracted: 52
- Estimated Token Budget: ~130756 tokens
- Canonical Reference: https://codewiki.google/github.com/ThinkInAIXYZ/deepchat
