Skip to content

Latest commit

 

History

History
1502 lines (1104 loc) · 42.2 KB

File metadata and controls

1502 lines (1104 loc) · 42.2 KB

Music Agent 架构设计 v1.2

状态: V1 实现前架构基线已冻结;v1.2 修正 Python 最低版本并补全异步运行时、时间与并发闭环
目标: Windows-first、Local-first、单用户个人音乐 Agent
主要技术栈: Python + TypeScript
设计优先级: 轻量运行、低空闲资源占用、低 Token 成本、清晰插件边界、易维护、功能可独立开发


1. 产品范围

Music Agent 是桌面个人音乐助手,而不是传统播放器,也不是泛用聊天机器人。

V1 聚焦:

  • 通过自然语言或直接控制本地/外部音乐播放;
  • 本地曲库与歌单管理;
  • 个性化推荐、每日推荐与场景推荐;
  • 音乐分析与解释;
  • 音乐相关对话;
  • 轻量长期个性化;
  • 以插件为核心的业务能力扩展。

V1 定位为单设备、Local-first。以下内容不属于 V1:多设备同步、云账号体系、内置本地基础模型、插件市场、复杂工作流引擎、高级编曲/生成、大规模自治 Agent Graph。


2. 技术栈

2.1 冻结技术栈

V1 使用 Python + TypeScript

推荐默认分工:

  • Python: Core 调度、插件框架、Provider、SQLite/Repository、AI/模型/音频相关业务逻辑;
  • TypeScript: 桌面 UI、交互层、视觉状态、前端事件与用户控制。

这是实现阶段的默认建议,而不是要求所有架构对象必须绑定某一种语言。第一方插件应尽量使用项目统一技术栈。Core 可以知道插件的 runtime 类型和兼容性要求。

2.2 Python 兼容范围

V1 Core 与第一方 Python 代码的最低版本要求为:

Python >= 3.10

项目级版本约束定义为:

>=3.10

工程约束:

  • V1 的验证基线至少覆盖 Python 3.10、3.11、3.12;这三个版本必须运行核心 Contract、Plugin Runtime、异步 Runtime 与 E2E Pipeline 测试;
  • 不人为设置 <3.13 一类上限。更新的 Python 版本可以使用,但只有进入 dev-harness/CI 验证矩阵后才视为正式验证版本;
  • Core 与第一方插件不得无条件使用高于 3.10 才出现的语法或标准库 API;确有需要时必须提供兼容实现;
  • 第三方 process 插件由自身完整 runtime.command 决定解释器和运行环境,Core 不替插件拼 Python 命令;
  • 插件可以声明自己的兼容性元数据,但不得提高 Core 的最低 Python 版本要求。

2.3 运行原则

  • 重型模型/音频进程按需加载;
  • 空闲状态不应无必要常驻分析或模型 worker;
  • V1 不内置 LLM 权重;
  • 优先事件驱动更新,避免轮询;
  • 当任务结构已经明确时,优先确定性本地执行,而不是调用 LLM。

3. 顶层架构

                         Desktop UI (TypeScript)
                           │ request     ▲ CoreResponse
                           ▼             │
                       Core Transport / Reply Channel
                                 │
                                 ▼
                           InputGateway
                                 │
                                 ▼
                        RequestCoordinator
                                 │
                                 ▼
                            IntentRouter
                                 │
                                 ▼
                          ContextResolver
                                 │
                                 ▼
                            TaskRouter
                                 │
                                 ▼
                         ExecutionPolicy
                                 │
                        direct / CoreTaskQueue
                              + TaskSupervisor
                                 │
                                 ▼
                           PluginManager
                                 │
               ┌─────────────────┴─────────────────┐
               ▼                                   ▼
          Builtin Plugin                      Process Plugin
               │                                   │
               └────────── Unified Contract ───────┘
                                 │
                                 ▼
                            PluginResult
                                 │
                                 ▼
                         ResultValidator
                                 │
                                 ▼
                         StateCoordinator
                        /                 \
                       ▼                   ▼
              RuntimeStateStore         SQLite
                        \                 /
                         └───────┬───────┘
                                 │ committed result
                                 ▼
                        RequestCoordinator
                         │                 │
                         │                 └─ emit notification Event
                         ▼                            │
                     CoreResponse                    ▼
                         │                  Lightweight EventBus
                         ▼                            │
                Core Transport / Reply Channel       ▼
                         │                     UI / Core observers
                         ▼
                    Desktop UI

两套旁路的轻量运行时索引支撑整条请求链:

CapabilityRegistry  -> 当前有哪些能力及其契约
ProviderRegistry    -> 当前配置并可用的公共/外部 Provider

两者都由配置文件和插件 Manifest 构建为轻量内存快照。正常请求不会反复读取或解析 YAML。


4. 请求流水线

请求流水线保持单向。下游模块不得重新解释上游已经确定的决策。

Input
 -> RequestCoordinator
 -> IntentRouter
 -> ContextResolver
 -> TaskRouter
 -> ExecutionPolicy
 -> Plugin execution
 -> Result validation
 -> State commit
 -> Request completion
 -> CoreResponse
 -> original caller/UI

4.1 InputGateway

V1 输入类型:

UserInput
├── text
├── voice -> ASR -> text path
├── hotkey
└── UI event

SystemTrigger
├── timer
├── startup
├── track_finished
└── plugin/runtime event

物理快捷键/UI 明确命令可以直接映射到已知 Capability,绕过语义 NLP。

4.2 RequestCoordinator

负责请求生命周期与澄清状态。

职责:

  • 分配 request_id
  • 检查并恢复 PendingRequest
  • 将澄清回答填回原始请求;
  • 协调取消、失败、完成;
  • 当一次用户请求明确要求多个 Capability 时,执行小型有序 TaskChain

PendingRequest 属于 RequestCoordinator,而不是 ContextResolver。

4.3 End-to-End 请求闭环

RequestCoordinator 不只负责把请求送入流水线,还负责维护请求生命周期直到终态accepted 只代表 Core 已经接管任务,绝不代表异步请求已经完成。

统一应用层响应 Envelope:

CoreResponse {
    request_id,
    status,              # accepted | completed | needs_clarification | failed | cancelled | timed_out | rejected | interrupted
    terminal,            # bool
    data,
    error,
    task_ids[],
    clarification        # optional
}

核心规则:

  • PluginResultPlugin -> Core 的任务级内部结果;CoreResponseCore -> UI/调用方 的请求级结果;
  • CoreResponse(status=accepted, terminal=false) 只是 ACK;异步请求必须继续进入终态;
  • needs_clarification 对应 WAITING_INPUT,同样不是终态;
  • 只有 completed / failed / cancelled / timed_out / rejected / interrupted 属于终态;
  • completed 只能在 PluginResult 验证成功、必要状态提交完成、TaskChain 已完成后生成;
  • UI 通过 request_id 查询权威 RequestState,不依赖 EventBus 猜测生命周期;Event 仅用于提示“状态已变化”;
  • 请求级响应组装必须确定性执行,不为了润色结果隐式调用 LLM。

4.4 RequestState 生命周期

V1 明确定义请求状态机:

RECEIVED
  -> RESOLVING
  -> READY
  -> RUNNING ----------------------------┐
  -> QUEUED -> RUNNING                   │
  -> WAITING_INPUT -> RESOLVING          │
                                         ▼
                                   COMMITTING
                                         │
                 ┌───────────────────────┼───────────────────────┐
                 ▼                       ▼                       ▼
             COMPLETED                FAILED              CANCELLED
                                         │
                                  TIMED_OUT / REJECTED /
                                      INTERRUPTED

RequestState 至少记录:

request_id
state
task_ids[]
created_at
updated_at
terminal_at        # optional
error              # optional

异步请求第一次返回 accepted 后,RequestState 仍继续更新,直到终态。Core 必须提供按 request_id 查询当前状态与最终结果的接口。

4.5 同步闭环

同步/快速任务:

request
 -> pipeline
 -> PluginResult
 -> validate
 -> StateCoordinator commit
 -> RequestState = COMPLETED
 -> CoreResponse(completed, terminal=true)
 -> caller

任何失败、取消或超时同样必须先提交对应终态,再返回终态 CoreResponse。

4.6 异步闭环

耗时任务:

request
 -> Task accepted into CoreTaskQueue
 -> RequestState = QUEUED/RUNNING
 -> CoreResponse(accepted, terminal=false)
 -> caller

background lifecycle
 -> queue/start/run
 -> PluginResult or error/cancel/timeout
 -> validate
 -> StateCoordinator commit if applicable
 -> TaskState terminal
 -> RequestState terminal
 -> persist terminal outcome
 -> emit request/task notification Event

因此异步任务存在两个不同概念:

ACK        = Core 已接管任务
Terminal   = 任务真正完成/失败/取消/超时/中断

两者不得混淆。

Transport/UI 断开默认不取消已经 accepted 的异步任务。任务继续由 Core 持有;客户端重连后通过 request_id 查询 RequestState/最终结果。只有显式 cancel 请求才触发取消语义。

终态结果必须先写入权威 RequestState/持久记录,再发送通知 Event;即使 Event 丢失或 UI 当时离线,最终结果仍可查询。

4.7 TaskChain 闭环

TaskChain 中每个 Task 都有独立 task_id,但共享同一个 request_id。只有链中所有必需 Task 完成后 RequestState 才能进入 COMPLETED

Task 1 terminal success
 -> apply ResultBinding
 -> materialize Task 2
 -> execute Task 2
 -> ...
 -> final state commit
 -> RequestState COMPLETED

任意必需 Task 出现失败、超时、取消或 Binding 失败,链立即停止,并以同一个 request_id 形成明确终态。

5. IntentRouter

IntentRouter 是唯一负责理解“当前用户指令语义”的层。

5.1 Intent 表示

domain + action + target + entities + constraints + references

示例:

{
  "capability_id": "recommendation.recommend",
  "domain": "recommendation",
  "action": "recommend",
  "target": null,
  "entities": {},
  "constraints": {
    "scene": "night_coding",
    "energy": "low"
  }
}

5.2 三层解析

Tier 0  Direct Event
        hotkey / exact UI event -> capability

Tier 1  Deterministic Language
        aliases / phrase patterns / simple slot extraction

Tier 2  Semantic Resolution
        只有确实需要语义理解时才调用 LLM

LLM 只能从允许的 Capability 和字段中选择,不能创造任意 domain/action。

5.3 与 CapabilityRegistry 的关系

CapabilityRegistry 不是流水线中的顺序阶段,而是旁路只读契约源。

IntentRouter 使用方式:

CapabilityRegistry
      │
      ├─ pre-constraint -> 约束允许的 capability / fields
      │
      └─ post-validation -> 本地 schema 校验

Post-validation 完全确定性执行,不会进行第二次 LLM 校验


6. ContextResolver

ContextResolver 不重新解析自然语言,也不重新选择 Capability。

职责只包括:

  1. 引用绑定;
  2. 按需上下文补充;
  3. 一致性/可用性检查。

示例:

current_track       -> 具体歌曲
foreground_provider -> 具体播放来源
previous_result     -> 仅在明确需要时绑定上一轮结果/推荐

6.1 上下文优先级

当用户指令本身与上下文强相关且存在歧义时:

  • current_track 与结构化 ConversationState 是并列第一优先级;
  • 只有两者不足时,才有限读取 previous_*
  • 禁止无限历史搜索;
  • V1 ContextResolver 不调用语义 LLM;
  • 仍无法解析时返回澄清,而不是继续猜测。

6.2 上下文存储

运行时状态可以包括:

  • PlaybackState;
  • InteractionFocusState;
  • TaskState;
  • ConversationState;
  • EnvironmentState。

持久上下文可以包括:

  • Taste Profile;
  • 播放历史;
  • 长期记忆;
  • 推荐历史;
  • 分析缓存。

只读取当前 Capability 声明为相关的上下文。


7. TaskRouter 与 TaskDescriptor

TaskRouter 不再理解语言。它把已解析的请求转换成精准业务执行契约。

核心原则:

TaskDescriptor 必须足够完整,使下游执行层不需要再次解释用户意图。

最小 Envelope:

TaskDescriptor {
    task_id,
    request_id,
    capability_id,
    input,
    execution_requirements
}

input 由插件定义,并按照 Capability 的 input schema 进行验证。

execution_requirements 只声明下游必须检查的执行条件,例如 Provider/资源是否可用,以及 Capability/插件给出的时间与并发约束提示。真正的绝对 deadline、排队时间和运行时间由 CoreTaskQueue/TaskState 计算与持有。

当原始用户请求明确包含多个操作时,TaskRouter 可以生成一个小型有序 TaskChain

示例:

"推荐三首然后播放"

Task 1: recommendation.recommend
Task 2: playback.play

RecommendationPlugin 自己不会决定播放任何内容。

7.1 TaskChain 的结果绑定

多插件协作时,TaskRouter 必须在执行前把依赖关系描述清楚,Core 不允许在执行过程中临时猜测“上一个结果应该塞到哪里”。

V1 使用轻量 ResultBinding

ResultBinding {
    from_task_id,
    source_path,
    to_task_id,
    target_path
}

例如“推荐三首然后播放第一首”:

Task 1: recommendation.recommend
Task 2: playback.play
Binding: task1.data.tracks[0] -> task2.input.track

RequestCoordinator 在 Task 1 获得 ValidatedPluginResult 后应用 Binding,物化 Task 2 的最终 TaskDescriptor,再交给 ExecutionPolicy。Binding 失败时链路停止并返回结构化失败,不允许插件之间直接传递对象。


8. ExecutionPolicy

ExecutionPolicy 刻意保持简单和确定性。

不得

  • 调用 LLM;
  • 重新解释用户语言;
  • 修改 Capability;
  • 重新设计 Task;
  • 搜索无关 Context;
  • 创建自治工作流。

它只检查 TaskDescriptor 声明的资源/Provider 条件当前是否满足。

TaskDescriptor
      ↓
check declared requirements
      ↓
EXECUTABLE / NOT_EXECUTABLE

如果到这一层仍需要语义猜测,说明上游解析失败,应返回澄清。

缓存查询、算法选择、Analysis/Recommendation 内部调用 LLM、SQLite 查询等业务内部决策全部属于对应插件。


9. CoreTaskQueue

V1 使用轻量、受控的异步任务运行时,而不是 Workflow Engine。CoreTaskQueueTaskSupervisor 一起负责所有后台任务生命周期。

9.1 单一异步运行时原则

Python Core 默认只有一个主 asyncio 事件循环负责:

  • RequestCoordinator;
  • TaskQueue 调度;
  • Provider 异步 I/O;
  • Process Plugin stdin/stdout;
  • EventBus 通知;
  • Task/Request 状态迁移。

禁止业务模块随意创建裸 threading.Thread、私有事件循环或无限制 Executor。

所有后台 coroutine 必须由 TaskSupervisor 创建并登记。除 TaskSupervisor/Runtime 基础设施外,业务代码禁止直接 fire-and-forget asyncio.create_task(...)。TaskSupervisor 持有 task handle、request/task identity、deadline 与取消入口,确保每个后台任务都可追踪、可等待、可回收。Timer/SystemTrigger 也复用同一个事件循环调度,不为每个定时器创建线程。

9.2 线程与进程边界

运行规则:

async-native I/O
 -> 主 asyncio event loop

短时、不可异步化的阻塞库调用
 -> Core 统一的 bounded blocking executor

长时间 CPU-bound / 不可安全取消 / 重型模型任务
 -> process plugin

V1 不允许每个插件私自维护线程池。共享 blocking executor 必须有固定 max_workers,其具体值属于配置,但不能无限增长。

关键限制:Python 线程不能被安全强杀。因此进入 blocking executor 的操作必须是短时且有界的。长时间工作如果需要可靠超时/取消,必须采用 process runtime。

9.3 队列、优先级与背压

CoreTaskQueue 必须有界:

max_queue_size
max_running_tasks

V1 只需要两个优先级:

interactive   # 用户主动请求
background    # timer / scan / refill / maintenance

交互任务优先,但不能无限饿死后台任务。队列已满时必须明确返回 rejected / QUEUE_FULL,禁止无限堆积内存。

Core 同时维护:

  • 全局并发上限;
  • 每 Plugin/Provider 的并发上限;
  • dedup key(适用于重复 SystemTrigger/扫描任务)。

9.4 时间预算模型

所有时间管理使用 monotonic clock 做运行时判断;wall-clock 只用于日志和持久化时间戳。

每个 Task 至少受以下预算控制:

queue_timeout          # 最长等待执行时间
execution_timeout      # 真正运行最长时间

Process Runtime 另外拥有:

process_start_timeout
handshake_timeout
cancel_grace_period
process_idle_timeout
shutdown_grace_period

外部请求还可以定义整体 request_deadline。TaskChain 每一步只能消费剩余预算,不能在每个阶段重新获得一整份 timeout。

具体默认秒数属于实现配置,但上述预算类别和语义属于冻结架构。

Provider retry、LLM retry、Process restart 等内部重试必须消费当前 Task 的剩余 execution/request deadline;每次 retry 不得重置完整 timeout。若剩余预算不足以安全开始下一次尝试,直接进入超时/失败终态。

9.5 TaskState 状态机

CREATED
 -> QUEUED
 -> STARTING        # process task only when needed
 -> RUNNING
 -> COMMITTING
 -> SUCCEEDED

terminal alternatives:
FAILED
CANCELLED
TIMED_OUT
REJECTED
INTERRUPTED

TaskState 是异步生命周期的权威事实源,EventBus 只广播其变化。

9.6 取消语义

取消必须按执行形态明确处理:

Queued Task

remove/skip from queue
-> CANCELLED

async coroutine / async Provider

cooperative cancel
-> wait cancellation grace
-> CANCELLED or FAILED

blocking executor task

Python 线程不能安全终止。Core 标记 cancellation requested,并丢弃晚到结果;因此这类工作必须短时有界,不能承载重型任务。

process plugin task

send cancel message
 -> wait cancel_grace_period
 -> terminate process if still running
 -> hard kill only as last resort
 -> terminal TaskState

任何晚到的 PluginResult 都必须依据 task generation/state 被丢弃,不能把已取消/超时任务重新提交为成功。

9.7 Process 并发模型

V1 每个 Process Plugin 实例默认只允许 1 个 active invocation。这样 stdin/stdout 协议无需在一个 worker 内处理复杂并发复用。

需要并发时,由 CoreTaskQueue 排队。未来如果证明有必要,可以增加多实例 worker pool,但不改变 TaskDescriptor/PluginResult 协议。

9.8 SQLite 与共享状态线程安全

  • Plugin 不直接写 Core SQLite;
  • SQLite 统一通过 Repository/Persistence Gateway;
  • 写操作必须串行化(例如单 writer/单 DB worker 或等价机制);
  • RuntimeStateStoreStateCoordinator 的状态提交在 Core 主事件循环上串行化;
  • 后台线程/Process 返回的结果必须回到 Core loop 后才能修改全局状态。

9.9 应用关闭与异常恢复

关闭顺序固定:

stop accepting new work
 -> reject/cancel queued work
 -> request cooperative cancellation for running work
 -> wait shutdown_grace_period
 -> shutdown process plugins
 -> terminate/kill remaining child processes
 -> flush state/persistence
 -> exit

V1 不自动恢复应用退出前仍在运行的任务。若持久化记录中存在非终态 Request/Task,下一次启动时统一标记为 INTERRUPTED,由用户或 SystemTrigger 决定是否重新提交。

9.10 CoreTaskQueue 职责边界

负责:

  • enqueue / bounded queue;
  • priority;
  • deduplicate;
  • concurrency control;
  • timeout/deadline;
  • cancel;
  • task status;
  • completion/failure propagation;
  • process runtime lifecycle coordination。

明确排除:

NO generic DAG
NO autonomous replanning
NO LangGraph-style orchestration
NO plugin-to-plugin dispatch
NO unmanaged threads

10. 插件架构

业务功能采用 Plugin-first。

V1 冻结六类插件:

BasePlugin
├── PlaybackPlugin
├── PlaylistPlugin
├── LibraryPlugin
├── RecommendationPlugin
├── AnalysisPlugin
└── ConversationPlugin

Category 层级主要用于开发规范和 Plugin SDK。Python 实现时可使用抽象基类、Protocol 或等价机制;具体语法属于实现设计。

10.1 Category 与 Capability

Category   = 这个插件属于什么类型
Capability = Core 可以调度的具体操作

示例:

plugin:
  id: builtin.recommendation
  category: recommendation

capabilities:
  - id: recommendation.recommend

Core 依据 Capability 调度,而不是依据 Category 写分支。

10.2 插件隔离

插件之间禁止直接调用。

非法:

RecommendationPlugin -> PlaybackPlugin

合法:

RecommendationPlugin
 -> PluginResult
 -> Core
 -> next TaskDescriptor
 -> PlaybackPlugin

这样可以避免隐式副作用,并保证插件能够独立开发和测试。

10.3 UI 不是插件

UI 属于应用层。桌面窗口、动画、视觉状态和 UI 交互是应用代码能力,不属于业务插件。


11. 最小稳定插件契约

Core 只冻结三个跨边界 Envelope:

PluginManifest
TaskDescriptor
PluginResult

11.1 最小 PluginManifest

必填字段:

api_version: music-agent/v1

plugin:
  id: builtin.playback
  version: 1.0.0
  category: playback

runtime:
  type: builtin

capabilities:
  - id: playback.pause

冻结的最小必填字段:

  • api_version
  • plugin.id
  • plugin.version
  • plugin.category
  • runtime.type
  • 至少一个 capabilities[].id

可选、由 Capability/Plugin 自己拥有的字段:

  • input_schema
  • output_schema
  • intent patterns;
  • context requirements;
  • config
  • config_schema
  • metadata
  • extensions

适用时,缺省 input/output schema 等价于空/默认契约。

Core 协议字段严格校验;插件自定义配置和扩展字段按插件自身 Schema 或插件逻辑校验。

11.2 统一调用

概念上所有插件统一暴露:

initialize
invoke(TaskDescriptor) -> PluginResult
shutdown

具体 Python 类/方法语法属于实现设计。

11.3 PluginResult

PluginResult {
    task_id,
    status,
    data,
    error,
    observations[]
}

data 是 Capability 定义的业务输出。

observations 用于报告可能影响 Core 全局状态的事实。

Core 接受插件输出前,会按照 Capability output schema 验证结果。


12. Plugin Runtime

V1 只冻结两种 runtime:

builtin
process

12.1 Builtin

Builtin 插件运行在主应用进程内,并通过统一插件框架注册。

轻量第一方能力优先采用 builtin,以避免 IPC 与子进程开销。

12.2 Process

每个 process 插件必须声明自己的完整启动命令。Core 不根据插件实现语言拼装命令。

推荐 Manifest:

runtime:
  type: process
  command:
    - python
    - main.py
    - --stdio
  cwd: .

command 必须使用 argv 数组,而不是 shell 字符串。

Core 通过 asyncio 子进程能力(或等价异步机制)管理 Process Plugin,不为每个子进程创建独立读取线程。

Core 负责:

read command
 -> spawn
 -> startup timeout
 -> handshake timeout
 -> stdin/stdout JSON
 -> invoke
 -> execution timeout / cancel
 -> idle cleanup
 -> shutdown

12.3 Process IPC

传输固定为:

stdin/stdout JSON Lines

V1 消息类型:

hello
invoke
result
error
cancel
shutdown
progress   # optional

重要约束:

  • stdout 只允许协议 JSON;插件日志必须写入 stderr,避免破坏消息帧;
  • 每个消息都必须包含可以关联调用的 request_id/task_id 或 invocation id;
  • 默认每个 Process Plugin 实例同一时间只有一个 active invocation;
  • Handshake 至少校验 plugin ID、plugin version、API version;
  • 进程启动、握手、调用、取消和关闭分别受对应时间预算控制;
  • Process crash 后当前任务进入失败/中断终态;下一次调用可由 PluginManager 按策略重新懒启动;
  • 已超时/取消任务的晚到结果必须丢弃。

Process 插件可以懒启动,并在 process_idle_timeout 后退出,非常适合音频/模型密集型 worker。

13. PluginManager

PluginManager 是基础设施,不负责业务编排。

职责:

configuration CRUD
manifest loading
manifest validation
runtime loading/unloading
plugin lookup
registry refresh

它不负责 Intent 解析、Context 推理、业务规划或插件协作。

13.1 插件管理状态持久化

Core 使用独立插件管理配置,例如:

version: 1

plugins:
  - id: builtin.playback
    enabled: true
    manifest: ./plugins/playback/manifest.yaml

  - id: custom.analysis
    enabled: false
    manifest: ./plugins/custom-analysis/manifest.yaml

职责分离:

Plugin Manifest
= 插件声明自己是谁、会做什么

Plugin management config
= Core 记录安装/引用/启用状态

安装、启用、禁用、更新、删除操作只修改该配置并刷新运行时快照。请求阶段使用内存索引,不重复读取 YAML。


14. CapabilityRegistry

CapabilityRegistry 是紧凑的只读运行时索引。

它回答:

当前系统具备哪些 Capability,它们的契约是什么?

它由已启用插件的 Manifest 派生。

不同模块读取窄视图:

IntentRouter       -> routing/schema view
ContextResolver    -> context requirement view
TaskRouter         -> plugin/handler ownership view
ExecutionPolicy    -> execution requirement view

CapabilityRegistry 不执行业务逻辑。


15. ProviderRegistry 与公共 Provider

ProviderRegistry 表示已经配置的公共/外部资源 Provider 及其可用性。

V1 最重要的公共 Provider 是 LLM Provider。需要 LLM 的业务插件统一使用 Core 提供的公共接口,不自行维护独立 endpoint/API Key。

15.1 LLM 连接模式

V1 支持两种配置方式。

商业 API 模式

llm:
  mode: api
  provider: openai
  model: model-name
  api_key_ref: openai-main

适用于使用用户自行购买 API 凭证的商业模型服务。

服务化 Endpoint 模式

llm:
  mode: endpoint
  endpoint: http://127.0.0.1:8000/v1
  model: qwen-model-name
  api_key_ref: optional-token

适用于本地或远端部署的模型服务,例如 OpenAI-compatible endpoint。

15.2 Provider 职责

公共 Provider 层负责:

  • endpoint/SDK transport;
  • credentials;
  • model name;
  • timeout/retry policy;
  • provider availability;
  • request/response normalization;
  • 可选 usage/logging statistics。

Provider 的 timeout/retry 必须接受 Core 当前 Task 的剩余时间预算约束;Provider 不能通过内部重试绕过 Task deadline。

插件拿到的是 Provider Interface,而不是裸 API Key。

ProviderRegistry 也可以管理其他确有必要的公共/外部连接,但 V1 不把它设计成万能 Service Container。


16. 播放来源策略

本地播放与外部音乐客户端是平等来源,不冻结永久优先级。

解析规则:

  1. 用户明确指定来源时,以用户指令为准;
  2. 未指定时,使用高置信度上下文,例如当前/最近活跃播放器;
  3. 仍存在冲突时询问用户。

用户明确指定的 Provider 不允许静默 fallback 到其他 Provider。


17. 状态与事件闭环

V1 冻结采用 同步状态提交 + 轻量通知事件

EventBus 不是事实源。

PluginResult
    ↓
ResultValidator
    ↓
StateCoordinator
    ├── 同步更新 RuntimeStateStore
    ├── 同步持久化必要 SQLite 记录
    └── 状态更新被接受后
            ↓
        emit notification Event
            ↓
        UI / Core observers

17.1 Observation 与 Event

Observation
= 插件报告的事实

Event
= Core 完成状态应用后发布的系统确认通知

例如:

PlaybackPlugin
 -> observation: playback_started
 -> StateCoordinator 更新 current_track/status/history
 -> event: playback_state_changed

插件不能直接修改应用全局状态或 UI 状态。

17.2 V1 基础 Observation 类别

分类保持刻意精简:

  • playback started/paused/stopped/track changed;
  • playlist created/updated;
  • library changed/scanned;
  • recommendation generated;
  • analysis completed;
  • conversation state changed;
  • plugin/task status changed。

17.3 V1 基础通知事件

同样保持粗粒度:

playback_state_changed
playlist_changed
library_changed
recommendation_ready
analysis_ready
conversation_changed
task_status_changed
plugin_status_changed

Event 只用于通知/刷新;发布 Event 时 Core 状态必须已经有效。

17.4 状态提交与请求完成的顺序

同步任务严格顺序:

ValidatedPluginResult
 -> StateCoordinator.apply()
 -> CommittedResult
 -> TaskState SUCCEEDED
 -> RequestCoordinator completes request/task-chain
 -> RequestState COMPLETED
 -> CoreResponse(completed)
 -> EventBus notification

异步任务严格顺序:

CoreTaskQueue accepts
 -> TaskState QUEUED/RUNNING
 -> CoreResponse(accepted, terminal=false)
 -> ...background work...
 -> ValidatedPluginResult
 -> StateCoordinator.apply()
 -> terminal TaskState
 -> terminal RequestState
 -> persist terminal outcome
 -> emit task/request notification Event

StateCoordinator 返回结构化 CommittedResult,不能只发 Event。accepted 不是终态;UI 必须能通过 request_id 查询最终 RequestState/结果。

超时/取消时,如果业务状态尚未提交,则禁止应用晚到结果;如果不可逆外部副作用已经发生,则插件必须通过 Observation 报告事实,Core 按实际事实修正 RuntimeState,而不能伪装为“什么都没发生”。

18. 持久化与个性化

V1 使用 SQLite 作为嵌入式本地持久化。

候选结构化数据包括:

  • music library;
  • play history;
  • playlists;
  • recommendation history;
  • analysis cache;
  • taste profile;
  • 经过筛选的 long-term memory;
  • agent/application state;
  • 异步 RequestState / TaskState 的最小生命周期记录与终态结果。

异步生命周期记录不保存 coroutine/thread/process handle,只保存可持久化状态、identity、时间戳、错误和终态结果。应用重启时发现 QUEUED/STARTING/RUNNING/COMMITTING 等非终态记录,统一转换为 INTERRUPTED

大文件、封面、临时缓存等继续放文件系统。

个性化保持三个概念分离:

Taste Profile       -> 用户倾向喜欢什么
Long-term Memory    -> 哪些信息值得长期记住
Relationship Model  -> 助手应该如何与用户互动

不会把完整聊天记录自动塞进 Prompt,也不会把完整聊天记录直接等同于长期记忆。


19. AnalysisPlugin 边界

音乐分析是业务插件职责,不是 Core 永久子系统。

AnalysisPlugin 内部可以自由组合:

  • 公共 LLM Provider;
  • 多模态/音频理解模型 endpoint;
  • 轻量 DSP/音频元数据代码;
  • 本地分析库;
  • 插件私有缓存/算法。

Core 只看到类似以下 Capability:

analysis.basic
analysis.explain
analysis.compare

以及对应结构化结果。

此前的 Analysis Hub 概念仍可以存在于某一个 AnalysisPlugin 内部实现中,但它不再是 Core 架构依赖。


20. Recommendation 边界

RecommendationPlugin 只返回推荐结果。

它不得静默触发播放。

recommendation.recommend
 -> RecommendationResult

如果用户明确要求“推荐后播放”,Core 创建有序 TaskChain:

recommendation.recommend
        ↓ Core 绑定结果
playback.play

所有跨插件协作都遵守同一规则。


21. Conversation 边界

ConversationPlugin 处理无法明确映射为确定性领域命令的音乐相关对话。

它可以使用公共 LLM Provider,以及 Core 选择性提供的 Context、Taste、Memory 或分析结果,但不能成为绕过 Capability Routing 的万能后门,也不能直接操作其他插件。


22. V1 冻结设计原则

  1. 业务层 Plugin-first。
  2. 实现技术栈为 Python + TypeScript。
  3. 插件契约保持最小且稳定。
  4. Core 依据 Capability 调度。
  5. Plugin Category 用于开发结构,不用于 Core 分支调度。
  6. 插件之间禁止直接调用。
  7. 跨插件协作必须由 Core 显式编排。
  8. 推荐不等于播放。
  9. IntentRouter 独占当前语言理解职责。
  10. ContextResolver 只做绑定、补充和校验。
  11. TaskDescriptor 是完整业务执行契约。
  12. ExecutionPolicy 确定性执行,永不调用 LLM。
  13. LLM 调用统一通过 Core 公共 Provider。
  14. Process 插件自行声明完整启动命令。
  15. Process IPC 使用 stdin/stdout JSON。
  16. Plugin Manifest 归插件所有;插件管理状态归 Core 配置所有。
  17. Registry 是轻量运行时快照,不在每次请求时重复解析 YAML。
  18. 插件输入输出双向契约都必须验证。
  19. 插件报告 Observation;Core 独占全局状态修改。
  20. 状态同步提交;Event 只负责轻量通知。
  21. UI 是应用基础设施,不是插件。
  22. V1 不使用通用 Workflow/DAG Engine。
  23. 重型分析/模型 worker 懒加载,并可在空闲后退出。
  24. 优先 Direct/Local 的确定性执行,再考虑语义 LLM。
  25. Core 与第一方 Python 代码最低支持 Python 3.10;验证基线至少覆盖 3.10/3.11/3.12。
  26. 每一个外部请求必须通过 request_id 进入可查询生命周期;accepted 仅为 ACK,异步任务最终必须进入 completed/failed/cancelled/timed_out/rejected/interrupted 之一。
  27. TaskChain 的跨任务数据依赖必须由显式 ResultBinding 描述,禁止运行时猜测。
  28. Core 使用单一主异步事件循环;线程只允许通过受控 bounded executor 使用,重型/不可安全取消工作必须使用 process runtime。
  29. 所有后台任务必须受有界队列、并发上限、queue/execution timeout 和明确取消语义约束。
  30. 应用关闭时必须停止接收新任务、回收队列与子进程,并把遗留非终态任务标记为 INTERRUPTED。
  31. 所有后台 coroutine 必须由 TaskSupervisor 持有,禁止业务代码 fire-and-forget;UI/Transport 断开不隐式取消 accepted 任务。
  32. Provider/LLM retry 必须消费剩余 deadline,禁止通过重试重置 timeout。

23. V1 架构基线数据流

23.1 简单播放命令

"暂停"
 -> FastMatcher
 -> playback.pause
 -> ContextResolver 绑定当前活跃 provider
 -> TaskDescriptor
 -> ExecutionPolicy
 -> PlaybackPlugin
 -> PluginResult(playback_paused)
 -> StateCoordinator commit
 -> RequestCoordinator
 -> CoreResponse(completed)
 -> UI
 -> playback_state_changed notification

不需要 LLM。

23.2 仅语义推荐

"想听点凌晨城市快醒了但自己还没睡的感觉"
 -> IntentRouter Tier 2
 -> recommendation.recommend
 -> ContextResolver 按需增加 taste/history
 -> TaskDescriptor
 -> RecommendationPlugin
 -> 插件需要时调用 shared LLMProvider
 -> RecommendationResult
 -> StateCoordinator 持久化 recommendation history
 -> RequestCoordinator
 -> CoreResponse(completed, recommendations)
 -> UI
 -> recommendation_ready notification

不会隐式触发播放。

23.3 推荐后播放

"推荐三首适合写代码的,然后用 Spotify 播放"
 -> IntentRouter
 -> Core TaskChain
      1. recommendation.recommend
      2. playback.play
 -> RecommendationPlugin
 -> validated result
 -> Core 将选中的 track 绑定进 Task 2
 -> PlaybackPlugin
 -> StateCoordinator commit
 -> RequestCoordinator completes TaskChain
 -> CoreResponse(completed)
 -> UI

23.4 Process AnalysisPlugin

analysis request
 -> TaskDescriptor
 -> ExecutionPolicy
 -> CoreTaskQueue enqueue
 -> TaskState QUEUED
 -> CoreResponse(accepted, terminal=false)
 -> scheduler acquires global + plugin concurrency permit
 -> TaskState STARTING/RUNNING
 -> PluginManager lazily starts process if needed
 -> process_start_timeout + handshake_timeout
 -> stdin JSON invoke
 -> stdout JSON progress/result
 -> execution_timeout / cancellation supervision
 -> output validation
 -> StateCoordinator commit
 -> TaskState SUCCEEDED (or FAILED/CANCELLED/TIMED_OUT)
 -> RequestState terminal
 -> persist terminal outcome
 -> completion Event(request_id, task_id)
 -> UI receives notification and/or queries RequestState

任何 timeout/cancel 后到达的旧 result 都不能覆盖终态。Process 日志写 stderr,stdout 只承载 JSON Lines 协议。

24. 实现阶段再决定、不会阻塞架构的事项

以下内容应在实现/Benchmark 时决定,而不是重新打开大架构讨论:

  • Python 具体框架和 package 目录(最低版本已冻结为 >=3.10,首批验证矩阵至少 3.10/3.11/3.12);
  • TypeScript 具体桌面/UI 框架;
  • Python/TypeScript 在应用 UI/Core 之间的具体边界和 IPC;
  • SQLite 表、字段、索引和 migration 工具;
  • 具体 ASR/TTS Provider;
  • 具体 LLM 厂商、模型和 retry 参数;
  • 推荐排序权重;
  • Taste/Memory 保留与更新公式;
  • Context TTL;
  • AnalysisPlugin 内部使用的具体分析库/模型;
  • 不同外部音乐客户端的具体控制方式;
  • UI 视觉语言、尺寸与动画系统;
  • queue/execution/process-start/handshake/cancel-grace/idle/shutdown 等 timeout 的具体默认数值;其类别与语义已经冻结。

这些都属于冻结契约之下的实现选择。


25. V1.2 完成边界

只要实现遵守下列稳定边界,就认为架构已经足够冻结,可以进入开发:

InputGateway
RequestCoordinator
IntentRouter
ContextResolver
TaskRouter
ExecutionPolicy
CoreTaskQueue
TaskSupervisor
PluginManager
CapabilityRegistry
ProviderRegistry
StateCoordinator
RuntimeStateStore
SQLite repositories
Lightweight EventBus
CoreResponse / Reply Channel
ResultBinding

BasePlugin
├── PlaybackPlugin
├── PlaylistPlugin
├── LibraryPlugin
├── RecommendationPlugin
├── AnalysisPlugin
└── ConversationPlugin

下一阶段产物应是 Implementation Plan,而不是继续进行宽泛的架构重构。后续架构修改应由实际实现证据、Benchmark 或原型失败来驱动。

26. End-to-End 架构验收门禁

进入业务功能扩展前,dev-harness 必须至少覆盖以下真实纵向链路:

  1. 同步快路径: 暂停 -> Intent -> Context -> TaskDescriptor -> ExecutionPolicy -> PlaybackPlugin(fake/builtin) -> PluginResult -> StateCoordinator -> CoreResponse(completed),并断言状态提交早于响应完成;
  2. 异步 Process 正常路径: request -> CoreResponse(accepted, terminal=false) -> bounded queue -> process start/handshake -> invoke/result -> state commit -> terminal TaskState/RequestState -> completion Event,且 request_id/task_id 全程一致;
  3. 队列超时: Task 长时间未获得执行资格 -> TIMED_OUT,不会再被 worker 执行;
  4. 运行超时: Process/Provider 超过 execution deadline -> cancel -> grace -> terminate/kill(如需要)-> TIMED_OUT,晚到结果不得提交;
  5. 用户取消: queued 与 running 两类任务都能进入 CANCELLED,且不会产生错误的成功状态;
  6. 背压: bounded queue 满时返回 rejected/QUEUE_FULL,内存队列长度不得继续增长;
  7. 应用关闭: shutdown 停止接收新任务,回收队列和子进程;遗留非终态任务在下次启动被标记为 INTERRUPTED
  8. 并发所有权: Core/Plugin 不得创建未登记裸线程;blocking executor 的 worker 数受配置上限控制;业务代码不得绕过 TaskSupervisor fire-and-forget;
  9. 断线恢复: accepted 任务在 UI/Transport 断开后继续运行,重连可通过 request_id 查询同一终态;
  10. Retry deadline: Provider/LLM retry 不得重置 execution/request deadline;
  11. TaskChain: 上一步结果只通过显式 ResultBinding 进入下一 Task;任一步超时/取消/失败都正确终止整个 request lifecycle。

兼容性门禁至少在 Python 3.10、3.11、3.12 三个解释器版本运行核心 Contract、Plugin Runtime、Async Runtime 与 E2E 测试。

这些测试用于证明的不只是“组件能运行”,而是:

请求从进入 Core、经过时间/并发控制、插件执行、状态提交,到同步响应或异步终态,形成可查询、可取消、可回收、不会因晚到结果破坏状态的一致闭环。