P5.UA13-realtime-session-service · ADR-13 RealtimeSessionService 实施(议题 3.2 工程态隔离 + realtime/xilink 互斥)
Worker:ClaudeB · 后端 (backend-csharp) / 预计:1.0d / 优先级:P1 / 状态:dispatched 隔离:🧵 文件隔离(同 worktree 同 branch · 与 ADR-13 fork 2 + fork 3 + ADR-09 4 active fork 文件正交)
🔍 触发与解锁链
| 触发 | 状态 | 影响 |
|---|---|---|
| ADR-AIOS-13 v1.0 accepted(2026-06-01 19:21) | ✅ user accept | 8 fork ready · 用户首批 start fork 1+6 |
| 真值核查 #1(subagent · 2026-06-01 18:25) | ✅ AudioDeviceService 已就位 · sidecar 0 I/O · 单 engine instance | 本 fork 实施"独立 session 不动 user project" |
| ADR-12 fulfilled 🏆 | ✅ 5ea9806 收尾 | 7 类 MeasurementNode + WS 推送基础设施已就位 · 本 fork 复用 |
→ 本 fork zombie 解锁:Phase 2 fork 4(realtimeRunStore + 顶栏)+ fork 5(input/output device 面板)· 总 1.5d。
任务定义(基于 ADR-AIOS-13 v1.0 §2.4 + §3.3 + §11)
新建 RealtimeSessionService 独立 session 管理服务,实现 ADR-13 议题 3 决议:
核心范围:
1. RealtimeSessionService 单例(独立 session ID = xitest-realtime · 不写 audioEngineService 内部状态)
2. REST API:POST /api/realtime/start + POST /api/realtime/stop + GET /api/realtime/state
3. WS 推送:WS /ws/realtime/state 推送状态变化(heartbeat ≥ 500ms · state change 立即推)
4. 与 xilink engine 互斥(用户拍板 Q3.1=A · ADR §2.4):start realtime 时若 audioEngineService.IsRunning → 自动 stop xilink → 返 previousSessionStopped 字段
5. Ximind 兼容性 5 项(ADR §11):自描述 JSON / description 字段 / recovery_hints[] / 审计日志 data/realtime_test_projects/_audit.jsonl / 结构化 RealtimeError
不在本 fork 范围(留 fork ⅔):
- ❌ 不实施 BuiltinLinkRegistry(fork 2 · Services/Link/BuiltinLinks/)
- ❌ 不修改 AudioDeviceService.cs(fork 3 · 加 bypass-xilink 模式)
- ❌ 不写 /ws/realtime/stream PCM 推流(fork 3 · 复用 ADR-12 #8 toolKind 路由)
- ❌ 不写 sidecar 端任何代码(本 ADR 全程 sidecar 0 改动)
- ❌ 不动 contract-v1.0(realtime 走 v2 命名空间)
完整 prompt(直接复制粘贴 worker 终端)
[U-thread] P5.UA13-realtime-session-service
[部门] 后端 (backend-csharp) · 推荐 skill: dotnet-realtime-communication
[Worker CWD] d:/work/25_claude/workspace/AlgoDepartment/04_development/
[Occupies] P5.K-services-realtime(写 · 新建 Services/Realtime/ 命名空间) + P5.K-services-engine(read · 互斥逻辑读 audioEngineService.IsRunning)
[隔离] 🧵 文件隔离 · 仅写:
- backend_csharp/Services/Realtime/IRealtimeSessionService.cs(新建)
- backend_csharp/Services/Realtime/RealtimeSessionService.cs(新建)
- backend_csharp/Models/Realtime/RealtimeSessionState.cs(新建 record)
- backend_csharp/Models/Realtime/StartRealtimeRequest.cs(新建 record)
- backend_csharp/Models/Realtime/RealtimeError.cs(新建 record · Ximind 兼容)
- backend_csharp/Routes/RealtimeApiRoutes.cs(新建)
- backend_csharp/Routes/RealtimeWsRoutes.cs(新建)
- backend_csharp/Program.cs(扩展:注册 service + map 2 路由 · 行号正交 fork 2/3)
- backend_csharp/Tests/Services/Realtime/RealtimeSessionServiceTests.cs(新建 xunit)
与 ADR-13 fork 2 (Services/Link/BuiltinLinks/) + fork 3 (Services/Meter/AudioDeviceService.cs 仅扩展) 文件路径完全正交
与 ADR-09 4 active fork (Services/Plugin/ 等) 文件路径完全正交
[优先级] P1 · 1.0d · 关键路径 · 解锁 Phase 2 fork 4+5
[ADR] d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/ADR/ADR-AIOS-13-xitest-realtime-dual-mode.md
(必读 §2.4 RealtimeSessionService + §2.10 边界铁律 4 互斥 + §3.3 议题 3 业务行为契约 + §11 Ximind 兼容性 5 项)
[业务行为契约引用] ADR-13 §3.3(① 输入输出契约 + ② 收敛/成功判据 + ③ 失败回退 + ④ 用户操作流 + ⑤ e2e 真值)
[参考文档](绝对路径 · worker 必读)
- ADR-13:d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/ADR/ADR-AIOS-13-xitest-realtime-dual-mode.md
· §2.4 RealtimeSessionService 完整定义(record schema + 互斥规则)
· §2.10 边界铁律 第 4 项(realtime 与 xilink engine 互斥)
· §3.3 议题 3 业务行为契约(5 子段)
· §10 6 议题矩阵(议题 3.1 顶栏单按钮 + 议题 3.2 工程态隔离 → 本 fork 主战场)
· §11.5 RealtimeError 结构(code/message/human_readable_message/recovery_hints[]/context · 必填段)
- PCB:d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/processes/P_arch/ADR-AIOS-13/PROCESS.md
- 范式参考(已 zombie · ClaudeB 同部门标本):
· d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/prompts/done/P5.U-runtime-mode-refactor--b43c35a.md
(RuntimeTargetService 单例 + REST + WS handler 范式 · 7 预定义 + LEGACY_MAP)
· d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/prompts/done/P5.U-meter-tap-multi-tool--48cf0ba.md
(MeterTapService + 7 toolKind 路由 + WS 推送范式 · fork 3 将复用)
- 现状必读(主仓真值核查 · Step 1 必跑):
· backend_csharp/Services/AudioEngine/AudioEngineService.cs(必看 IsRunning 属性 + StartAsync/StopAsync 接口 · 本 fork 互斥逻辑读它)
· backend_csharp/Services/Meter/AudioDeviceService.cs(必看 EnumerateDevices · 本 fork InputDeviceId 校验需调它)
· backend_csharp/Program.cs(必看 service 注册顺序 + WebApplication.MapXxx 路由风格 · 模仿现有风格新增 RealtimeApiRoutes/RealtimeWsRoutes 注册)
· backend_csharp/Routes/MeterDevApiRoutes.cs(WS 推送范式 · 本 fork RealtimeWsRoutes 模仿)
【背景】
ADR-AIOS-13 v1.0 已 accepted(2026-06-01 19:21)。议题 3 决议:realtime 模式与 xilink user project 完全独立 · 同时只能 1 个 session active(audio device 抢占规避)。
3 路 subagent 主仓真值核查(2026-06-01 18:25)发现:
① C# AudioEngineService 当前是单 engine instance · 直接读 user project · 没"运行独立 session 不动 project state"机制
② 前端 xitest 不读 xilink project(天然解耦 · 改造点小)
③ 没有 RealtimeSessionService(本 fork 新建)
本 fork 核心:用 session ID 区分 xitest-realtime vs xilink user project · 二者互斥 start。
【执行步骤】(7 步 · 总 1.0d)
Step 1 · 真值核查 + git pull(必跑 · 0.05d)
- git status / git pull origin xistudio --no-rebase
- cat backend_csharp/Services/AudioEngine/AudioEngineService.cs | head -100
· 输出 IsRunning 属性签名 + StartAsync/StopAsync 签名 · 互斥逻辑要调用
- cat backend_csharp/Program.cs | head -80
· 输出 service 注册段(builder.Services.AddSingleton<...>) + map 路由段(app.Map...)的现有风格
- grep -r "AddSingleton" backend_csharp/Program.cs · 看 audioEngineService 注册方式 · 模仿
- 输出真值核查到 commit message body:
· audioEngineService 当前 IsRunning 签名:`???`
· StopAsync 签名:`???`
· Program.cs 路由注册风格:`???`
Step 2 · 落 5 个 model record(0.15d)
- backend_csharp/Models/Realtime/RealtimeSessionState.cs:
public record RealtimeSessionState(
string SessionId, // 永远 = "xitest-realtime"
string Status, // "idle" | "starting" | "running" | "stopping" | "error"
string? Mode, // "hardware" | "loopback" | null(idle 时)
string? InputDeviceId,
string? OutputDeviceId,
SignalConfig? SignalConfig, // null when mode=hardware
RealtimeError? Error,
long UpdatedAt // unix ms
);
public record SignalConfig(string Type, double? Frequency, double AmplitudeDbfs, string? Description);
- backend_csharp/Models/Realtime/StartRealtimeRequest.cs:
public record StartRealtimeRequest(
string Mode, // "hardware" | "loopback"
string InputDeviceId,
string ToolKind, // "fft"|"rms"|"transfer"|"phase"|"waveform"|"electrical"|"recorder"
int[] Channels,
int SampleRate,
int? FftSize,
string? OutputDeviceId, // loopback only
SignalConfig? SignalConfig, // loopback only
string? Description // Ximind 自然语言描述(可选)
);
- backend_csharp/Models/Realtime/RealtimeError.cs(ADR §11.5 结构):
public record RealtimeError(
string Code, // 枚举 8 个见下
string Message, // EN
string HumanReadableMessage,// 中文
string[] RecoveryHints,
Dictionary<string, object>? Context
);
// Code 枚举(常量 class):AUDIO_DEVICE_BUSY / AUDIO_DEVICE_NOT_FOUND / XILINK_STOP_FAILED /
// BUILTIN_LINK_LOAD_FAILED / SIDECAR_UNAVAILABLE / INVALID_REQUEST /
// SESSION_ALREADY_RUNNING / DEVICE_LOOPBACK_SAME_DEVICE
- StartResult / StopResult record(API response):见 step 3 注释
Step 3 · 落 IRealtimeSessionService + RealtimeSessionService(0.3d)
- IRealtimeSessionService.cs:
public interface IRealtimeSessionService
{
bool IsRunning { get; }
RealtimeSessionState GetState();
Task<StartResult> StartAsync(StartRealtimeRequest req, CancellationToken ct = default);
Task<StopResult> StopAsync(CancellationToken ct = default);
event Action<RealtimeSessionState>? OnStateChanged; // WS handler 订阅
}
public record StartResult(bool Success, RealtimeSessionState State, PreviousSessionStopped? PreviousSessionStopped, RealtimeError? Error);
public record StopResult(bool Success, RealtimeSessionState State, RealtimeError? Error);
public record PreviousSessionStopped(string Type, string? ProjectId); // type="xilink"
- RealtimeSessionService.cs 关键逻辑:
· ctor 注入 IAudioEngineService(read · 互斥) + IAudioDeviceService(read · device 校验) + ILogger
· _state 字段 · lock _stateLock 保护并发 start/stop
· IsRunning => _state.Status == "running"
· GetState() => _state(deep clone or record copy)
· StartAsync:
① 校验 req.Mode in {"hardware","loopback"} · req.InputDeviceId 存在(_audioDeviceService.GetDeviceById 校验)· loopback 时 outputDeviceId != inputDeviceId
② 若 _state.Status == "running" → 返 RealtimeError(SESSION_ALREADY_RUNNING) · 不动状态
③ 若 _audioEngineService.IsRunning → 调 _audioEngineService.StopAsync(ct) · 失败返 XILINK_STOP_FAILED · 成功记录 PreviousSessionStopped(Type="xilink", ProjectId=current)
④ 状态切 starting → running · 记录 mode/inputDeviceId/outputDeviceId/signalConfig · 写审计日志
⑤ 触发 OnStateChanged 事件 · 返 StartResult
· StopAsync:
① 若 IsRunning == false → 返 success(idempotent · 不报错)
② 状态切 stopping → idle · 清理 mode 等字段
③ 写审计日志 · 触发 OnStateChanged · 返 StopResult
· 审计日志路径:`data/realtime_test_projects/_audit.jsonl`(append-only · 不存在则创建目录 · ADR §11.4)
format:{"ts":<ms>,"event":"realtime.start"|"realtime.stop","sessionId":"xitest-realtime","mode":"...","inputDeviceId":"...","previousSessionStopped":{...}}
Step 4 · 落 RealtimeApiRoutes(REST · 0.15d)
- backend_csharp/Routes/RealtimeApiRoutes.cs:
public static class RealtimeApiRoutes
{
public static void MapRealtimeApi(this WebApplication app)
{
app.MapPost("/api/realtime/start", async (StartRealtimeRequest req, IRealtimeSessionService svc) =>
{
var r = await svc.StartAsync(req);
return r.Success ? Results.Ok(r) : Results.BadRequest(r);
});
app.MapPost("/api/realtime/stop", async (IRealtimeSessionService svc) =>
{
var r = await svc.StopAsync();
return Results.Ok(r);
});
app.MapGet("/api/realtime/state", (IRealtimeSessionService svc) =>
Results.Ok(svc.GetState()));
}
}
- Program.cs 加 1 行 `app.MapRealtimeApi();`(模仿现有 MeterDevApi 风格 · 行号正交 fork 2/3 加的注册行)
Step 5 · 落 RealtimeWsRoutes(WS · 0.15d)
- backend_csharp/Routes/RealtimeWsRoutes.cs:
public static class RealtimeWsRoutes
{
public static void MapRealtimeWs(this WebApplication app)
{
app.Map("/ws/realtime/state", async (HttpContext ctx, IRealtimeSessionService svc) =>
{
if (!ctx.WebSockets.IsWebSocketRequest) { ctx.Response.StatusCode = 400; return; }
using var ws = await ctx.WebSockets.AcceptWebSocketAsync();
// 1) 接受连接立即推当前 state(snapshot)
await SendStateAsync(ws, svc.GetState(), ctx.RequestAborted);
// 2) 订阅 OnStateChanged · 推送变化(用 Channel<T> 缓冲)
var channel = System.Threading.Channels.Channel.CreateUnbounded<RealtimeSessionState>();
Action<RealtimeSessionState> handler = s => channel.Writer.TryWrite(s);
svc.OnStateChanged += handler;
try
{
await foreach (var s in channel.Reader.ReadAllAsync(ctx.RequestAborted))
await SendStateAsync(ws, s, ctx.RequestAborted);
}
finally { svc.OnStateChanged -= handler; }
});
}
private static Task SendStateAsync(WebSocket ws, RealtimeSessionState state, CancellationToken ct) { ... JSON serialize + SendAsync ... }
}
- Program.cs 加 1 行 `app.MapRealtimeWs();`
Step 6 · 落 xunit 测试(0.15d)
- backend_csharp/Tests/Services/Realtime/RealtimeSessionServiceTests.cs · 8 case:
· GetState_Initial_ReturnsIdle
· StartAsync_Hardware_TransitionsToRunning_AndEmitsEvent
· StartAsync_Loopback_RequiresOutputDeviceId
· StartAsync_LoopbackSameDevice_ReturnsError(code=DEVICE_LOOPBACK_SAME_DEVICE)
· StartAsync_WhenAlreadyRunning_ReturnsError(code=SESSION_ALREADY_RUNNING)
· StartAsync_WhenXilinkRunning_StopsXilinkFirst_AndReturnsPreviousSessionStopped
· StopAsync_WhenIdle_IsIdempotent
· StopAsync_WhenRunning_TransitionsToIdle_AndEmitsEvent
- 用 Mock<IAudioEngineService> + Mock<IAudioDeviceService> 隔离依赖
Step 7 · build + test + 手动 e2e + commit(0.05d)
- cd backend_csharp && dotnet build(零错误零警告 · 基线 217/0)
- dotnet test(基线 +8 用例 · 217 → 225)
- 手动 e2e(若 sln 可启动):
· dotnet run · curl http://localhost:5000/api/realtime/state → idle 200 OK
· curl -X POST http://localhost:5000/api/realtime/start -H "Content-Type: application/json" -d '{"mode":"hardware","inputDeviceId":"<some-real-device>","toolKind":"fft","channels":[0],"sampleRate":48000}' → 200 + state.status="running"
· curl -X POST http://localhost:5000/api/realtime/stop → 200 + state.status="idle"
· 注:若 inputDeviceId 校验失败(无真硬件)是预期 · 此时 fork 3 zombie 后才有 device 真值校验
- git add backend_csharp/Services/Realtime/ backend_csharp/Models/Realtime/ backend_csharp/Routes/RealtimeApiRoutes.cs backend_csharp/Routes/RealtimeWsRoutes.cs backend_csharp/Program.cs backend_csharp/Tests/Services/Realtime/
- git commit(三元组 trailer 见下) · git push origin xistudio
【验收】
形式合规:
- [ ] dotnet build 零错误零警告
- [ ] dotnet test 全绿(基线 217/0 → 225/0 · +8 用例)
- [ ] 仅 9 文件修改:5 model + 1 interface + 1 service + 2 routes(API + WS)+ Program.cs(注册 2 行)+ 1 测试文件(共 ≤ 10 文件)
- [ ] 不动 contract-v1.0 / AudioDeviceService 内核(fork 3 才扩)/ AudioEngineService(只读)
- [ ] 不动 Services/Link/(fork 2 范围)
- [ ] 0 引入新 NuGet 包(若必须用 System.Threading.Channels 已在 .NET 运行时 · 无需新增)
业务行为契约(端到端真值 · 必跑 · ADR §3.3):
- [ ] curl GET /api/realtime/state(idle 时)→ 200 · status="idle" · sessionId="xitest-realtime"
- [ ] curl POST /api/realtime/start(hardware mode)→ 200 · status="running" · mode="hardware"
- [ ] curl POST /api/realtime/start(再次 · already running)→ 400 + RealtimeError(code=SESSION_ALREADY_RUNNING · recovery_hints 含 "stop current session first")
- [ ] curl POST /api/realtime/stop → 200 · status="idle"
- [ ] WS /ws/realtime/state 连接后立即收到 1 帧 snapshot · start/stop 时各推 1 帧
- [ ] 审计日志 data/realtime_test_projects/_audit.jsonl 每次 start/stop 各 append 1 行
Ximind 兼容性(ADR §11 · 必跑):
- [ ] RealtimeSessionState 字段名自描述(不是缩写)· JSON 序列化用 camelCase
- [ ] StartRealtimeRequest 含可选 description 字段(自然语言描述测量目的)
- [ ] RealtimeError 含 code + message + human_readable_message + recovery_hints[](≥ 1 项每个 error · 必含)
- [ ] 审计日志路径正确(data/realtime_test_projects/_audit.jsonl · 路径不存在自动创建目录)
【commit】
subject:`feat(P5.UA13-realtime-session-service): RealtimeSessionService + REST/WS API + xilink mutex · ADR-13 §2.4 §3.3 §11`
trailer(必须精确 · 三元组):
[step=7/7] [pid=P5] [uid=UA13-realtime-session-service] [occupies=P5.K-services-realtime+P5.K-services-engine(read)]
[files=Services/Realtime/IRealtimeSessionService.cs, Services/Realtime/RealtimeSessionService.cs, Models/Realtime/RealtimeSessionState.cs, Models/Realtime/StartRealtimeRequest.cs, Models/Realtime/RealtimeError.cs, Routes/RealtimeApiRoutes.cs, Routes/RealtimeWsRoutes.cs, Program.cs, Tests/Services/Realtime/RealtimeSessionServiceTests.cs]
[isolation] file(同 worktree 同 branch · 与 ADR-13 fork 2/3 + ADR-09 4 active 文件路径完全正交)
[derived_from] ADR-AIOS-13 v1.0 §2.4 + §3.3 + §11
[truth-check] AudioEngineService.IsRunning 签名=??? · StopAsync 签名=??? · Program.cs 注册风格=???(Step 1 输出真值)
[acceptance] curl /api/realtime/{state,start,stop} 4 用例全过 · WS /ws/realtime/state 推送验证 · xunit 8 用例全绿
[ximind] 5 项检查清单全过(自描述字段 · description 字段 · recovery_hints · 审计日志 · 结构化 error)
【禁止】(7 条红线)
1. ❌ 不修改 backend_csharp/Services/AudioEngine/AudioEngineService.cs(只读 IsRunning + StopAsync · 不能动其内部)
2. ❌ 不修改 backend_csharp/Services/Meter/AudioDeviceService.cs(fork 3 范围 · 本 fork 仅读 GetDeviceById 校验)
3. ❌ 不写 BuiltinLinkRegistry / Services/Link/BuiltinLinks/(fork 2 范围)
4. ❌ 不写 /ws/realtime/stream PCM 推流(fork 3 范围 · 本 fork 仅 /ws/realtime/state 元数据推送)
5. ❌ 不修改 contract-v1.0 任何字段(realtime 走 v2 命名空间 · 后续 ADR-14 才考虑)
6. ❌ 不省略 Step 1 真值核查报告(commit body 必含 AudioEngineService 实际签名)
7. ❌ 不省略 RealtimeError.recovery_hints[](Ximind 铁律 · 大模型基于此自主重试)
解锁链(本任务 zombie 后)
- ✅ Phase 2 fork 4 P0.UA13-realtime-run-store-toolbar(等本 fork + fork 3 zombie · realtimeRunStore 调 /api/realtime/start)
- ✅ Phase 2 fork 5 P0.UA13-input-output-device-config-panel(等本 fork + fork 2 zombie · 调 /api/builtin-links + /api/realtime/start)
- ✅ Phase 3 fork 8 P_e2e.UA13-truth(议题 3.2 互斥切换 e2e 验收)
- ✅ ADR-13 §3.3 议题 3 业务行为契约 ⑤ e2e 真值脚本可跑
风险评估
| 风险 | 缓解 |
|---|---|
| ⚠️ AudioEngineService.IsRunning 当前签名可能与 ADR §2.4 假设不符 | Step 1 真值核查必跑 · grep 出真实签名后再实施 · 若不一致(eg. 异步 IsRunningAsync)→ commit body 报告 + 适配 |
| ⚠️ WS Channel |
_stateLock 锁住状态切换 · OnStateChanged 在 lock 外触发(避免 deadlock)· Channel 单线程 writer 安全 |
| ⚠️ 审计日志目录 data/realtime_test_projects/ 不存在 | StartAsync 内 Directory.CreateDirectory(若不存在)· 不依赖 fork 6 storage 落盘(那是前端 IDB) |
| ⚠️ xilink stop 失败导致 realtime 卡 starting | 超时 5s · 失败返 XILINK_STOP_FAILED + recovery_hints=["manually stop xilink in xilink stage"] · 状态回 idle |
| ⚠️ 与 fork ⅔ 同 PR 合并冲突 Program.cs | 三 fork 都改 Program.cs 但行号正交(本 fork 加 MapRealtimeApi/Ws · fork 2 加 MapBuiltinLinkApi · fork 3 不动 Program.cs)· commit 时间错开 · git pull 自动合并 |
历史
| 时间 | 事件 | hash |
|---|---|---|
| 2026-06-01 19:30 | dispatched · 用户首批 start 4 fork 中关键路径 fork 1 · ADR-13 v1.0 accepted 9 分钟后派发 · ClaudeB 1.0d 🧵 file 与 fork ⅔ + ADR-09 4 active 文件正交 · 解锁 Phase 2 fork 4+5 | — |