perf(cqrs): 收口默认流式基准宿主

- 新增默认 stream benchmark 的 handwritten generated registry,并通过真实程序集注册路径接上 generated stream invoker provider

- 更新 StreamingBenchmarks 宿主接线、README 与 RP-107 recovery 文档,统一 request、pipeline、stream 默认宿主口径

- 更新 gframework-boot 与 gframework-batch-boot 技能,改为以上下文预算接近约 80% 为默认优先停止信号
This commit is contained in:
gewuyou 2026-05-08 12:47:24 +08:00
parent c82e981b7e
commit 24462b0035
7 changed files with 193 additions and 17 deletions

View File

@ -12,6 +12,10 @@ batches until a clear stop condition is met.
Treat `AGENTS.md` as the source of truth. This skill extends `gframework-boot`; it does not replace it.
Context budget is a first-class stop signal. Do not keep batching merely because a file-count threshold still has
headroom if the active conversation, loaded repo artifacts, validation output, and pending recovery updates suggest the
agent is approaching its safe working-context limit.
## Startup Workflow
1. Execute the normal `gframework-boot` startup sequence first:
@ -28,6 +32,11 @@ Treat `AGENTS.md` as the source of truth. This skill extends `gframework-boot`;
- repeated test refactor pattern
- module-by-module documentation refresh
- other repetitive multi-file cleanup
4. Before the first implementation batch, estimate whether the current task is likely to stay below roughly 80% of the
agent's safe working-context budget through one more full batch cycle:
- include already loaded `AGENTS.md`, skills, `ai-plan` files, recent command output, active diffs, and expected validation output
- if another batch would probably push the conversation near the limit, plan to stop after the current batch even if
branch-size thresholds still have room
## Baseline Selection
@ -67,8 +76,15 @@ For shorthand numeric thresholds, use a fixed default baseline:
Choose one primary stop condition before the first batch and restate it to the user.
When the user does not explicitly override the priority order, use:
1. context-budget safety
2. semantic batch boundary / reviewability
3. the user-requested local metric such as files, lines, warnings, or time
Common stop conditions:
- the next batch would likely push the agent above roughly 80% of its safe working-context budget
- branch diff vs baseline approaches a file-count threshold
- warnings-only build reaches a target count
- a specific hotspot list is exhausted
@ -76,6 +92,9 @@ Common stop conditions:
If multiple stop conditions exist, rank them and treat one as primary.
Treat file-count or line-count thresholds as coarse repository-scope signals, not as a proxy for AI context health.
When they disagree with context-budget safety, context-budget safety wins.
## Shorthand Stop-Condition Syntax
`gframework-batch-boot` may be invoked with shorthand numeric thresholds when the user clearly wants a branch-size stop
@ -108,6 +127,7 @@ When shorthand is used:
- current branch and active topic
- selected baseline
- current stop-condition metric
- current context-budget posture and whether one more batch is safe
- next candidate slices
2. Keep the critical path local.
3. Delegate only bounded slices with explicit ownership:
@ -127,7 +147,8 @@ When shorthand is used:
6. After each completed batch:
- integrate or verify the result
- rerun the required validation
- recompute the primary stop-condition metric
- recompute the primary stop-condition metric
- reassess whether one more batch would likely push the agent near or beyond roughly 80% context usage
- decide immediately whether to continue or stop
7. Do not require the user to manually trigger every round unless:
- the next slice is ambiguous
@ -158,6 +179,7 @@ For multi-batch work, keep recovery artifacts current.
Stop the loop when any of the following becomes true:
- the next batch would likely push the agent near or beyond roughly 80% of its safe working-context budget
- the primary stop condition has been reached or exceeded
- the remaining slices are no longer low-risk
- validation failures indicate the task is no longer repetitive
@ -165,6 +187,7 @@ Stop the loop when any of the following becomes true:
When stopping, report:
- whether context budget was the deciding factor
- which baseline was used
- the exact metric value at stop time
- completed batches

View File

@ -36,14 +36,18 @@ Treat `AGENTS.md` as the source of truth. Use this skill to enforce a startup se
- `simple`: one concern, one file or module, no parallel discovery required
- `medium`: a small number of modules, some read-only exploration helpful, critical path still easy to keep local
- `complex`: cross-module design, migration, large refactor, or work likely to exceed one context window
11. Apply the delegation policy from `AGENTS.md`:
11. Estimate the current context-budget posture before substantive execution:
- account for loaded startup artifacts, active `ai-plan` files, visible diffs, open validation output, and likely next-step output volume
- if the task already appears near roughly 80% of a safe working-context budget, prefer closing the current batch,
refreshing recovery artifacts, and stopping at the next natural semantic boundary instead of starting a fresh broad slice
12. Apply the delegation policy from `AGENTS.md`:
- Keep the critical path local
- Use `explorer` with `gpt-5.1-codex-mini` for narrow read-only questions, tracing, inventory, and comparisons
- Use `worker` with `gpt-5.4` only for bounded implementation tasks with explicit ownership
- Do not delegate purely for ceremony; delegate only when it materially shortens the task or controls context growth
12. Before editing files, tell the user what you read, how you classified the task, whether subagents will be used,
13. Before editing files, tell the user what you read, how you classified the task, whether subagents will be used,
and the first implementation step.
13. Proceed with execution, validation, and documentation updates required by `AGENTS.md`.
14. Proceed with execution, validation, and documentation updates required by `AGENTS.md`.
## Task Tracking
@ -69,6 +73,8 @@ For multi-step, cross-module, or interruption-prone work, maintain the repositor
first, then search the mapped active topics before scanning the broader public area.
- If the current branch and the mapped active topics describe the same feature area, prefer resuming those topics first.
- If the repository state suggests in-flight work but no recovery document matches, reconstruct the safest next step from code, tests, and Git state before asking the user for clarification.
- If the current turn already carries heavy recovery context, broad diffs, or long validation output, prefer a
recovery-point update and a clean stop over starting another large slice just because the code task itself remains open.
## Example Triggers

View File

@ -0,0 +1,96 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using GFramework.Core.Abstractions.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
using Microsoft.Extensions.DependencyInjection;
namespace GFramework.Cqrs.Benchmarks.Messaging;
/// <summary>
/// 为默认 stream steady-state benchmark 提供 hand-written generated registry
/// 以便验证“默认 stream 宿主吸收 generated stream invoker provider”后的完整枚举收益。
/// </summary>
public sealed class GeneratedDefaultStreamingBenchmarkRegistry :
GFramework.Cqrs.ICqrsHandlerRegistry,
GFramework.Cqrs.ICqrsStreamInvokerProvider,
GFramework.Cqrs.IEnumeratesCqrsStreamInvokerDescriptors
{
private static readonly GFramework.Cqrs.CqrsStreamInvokerDescriptor Descriptor =
new(
typeof(IStreamRequestHandler<
StreamingBenchmarks.BenchmarkStreamRequest,
StreamingBenchmarks.BenchmarkResponse>),
typeof(GeneratedDefaultStreamingBenchmarkRegistry).GetMethod(
nameof(InvokeBenchmarkStreamHandler),
BindingFlags.Public | BindingFlags.Static)
?? throw new InvalidOperationException("Missing generated default streaming benchmark method."));
private static readonly IReadOnlyList<GFramework.Cqrs.CqrsStreamInvokerDescriptorEntry> Descriptors =
[
new GFramework.Cqrs.CqrsStreamInvokerDescriptorEntry(
typeof(StreamingBenchmarks.BenchmarkStreamRequest),
typeof(StreamingBenchmarks.BenchmarkResponse),
Descriptor)
];
/// <summary>
/// 把默认 stream benchmark handler 注册为单例,保持与原先 steady-state 宿主一致的生命周期语义。
/// </summary>
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddSingleton(
typeof(IStreamRequestHandler<StreamingBenchmarks.BenchmarkStreamRequest, StreamingBenchmarks.BenchmarkResponse>),
typeof(StreamingBenchmarks.BenchmarkStreamHandler));
logger.Debug("Registered generated default streaming benchmark handler.");
}
/// <summary>
/// 返回当前 provider 暴露的全部 generated stream invoker 描述符。
/// </summary>
public IReadOnlyList<GFramework.Cqrs.CqrsStreamInvokerDescriptorEntry> GetDescriptors()
{
return Descriptors;
}
/// <summary>
/// 为目标流式请求/响应类型对返回 generated stream invoker 描述符。
/// </summary>
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out GFramework.Cqrs.CqrsStreamInvokerDescriptor? descriptor)
{
if (requestType == typeof(StreamingBenchmarks.BenchmarkStreamRequest) &&
responseType == typeof(StreamingBenchmarks.BenchmarkResponse))
{
descriptor = Descriptor;
return true;
}
descriptor = null;
return false;
}
/// <summary>
/// 模拟 generated stream invoker provider 为默认 stream benchmark 产出的开放静态调用入口。
/// </summary>
public static object InvokeBenchmarkStreamHandler(
object handler,
object request,
CancellationToken cancellationToken)
{
var typedHandler = (IStreamRequestHandler<
StreamingBenchmarks.BenchmarkStreamRequest,
StreamingBenchmarks.BenchmarkResponse>)handler;
var typedRequest = (StreamingBenchmarks.BenchmarkStreamRequest)request;
return typedHandler.Handle(typedRequest, cancellationToken);
}
}

View File

@ -18,6 +18,9 @@ using GFramework.Cqrs.Abstractions.Cqrs;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
[assembly: GFramework.Cqrs.CqrsHandlerRegistryAttribute(
typeof(GFramework.Cqrs.Benchmarks.Messaging.GeneratedDefaultStreamingBenchmarkRegistry))]
namespace GFramework.Cqrs.Benchmarks.Messaging;
/// <summary>
@ -59,12 +62,12 @@ public class StreamingBenchmarks
MinLevel = LogLevel.Fatal
};
Fixture.Setup("StreamRequest", handlerCount: 1, pipelineCount: 0);
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
_baselineHandler = new BenchmarkStreamHandler();
_container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
{
container.RegisterSingleton<GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler<BenchmarkStreamRequest, BenchmarkResponse>>(
_baselineHandler);
container.RegisterCqrsHandlersFromAssembly(typeof(StreamingBenchmarks).Assembly);
});
_runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
_container,
@ -86,7 +89,14 @@ public class StreamingBenchmarks
[GlobalCleanup]
public void Cleanup()
{
BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider);
try
{
BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider);
}
finally
{
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
}
}
/// <summary>

View File

@ -29,7 +29,7 @@
- `Messaging/NotificationBenchmarks.cs`
- `GFramework.Cqrs` runtime 与 `MediatR` 的单处理器 notification publish 对比
- `Messaging/StreamingBenchmarks.cs`
- direct handler、`GFramework.Cqrs` runtime 与 `MediatR` 的 stream request 完整枚举对比
- direct handler、已接上 handwritten generated stream invoker provider 的 `GFramework.Cqrs` runtime 与 `MediatR` 的 stream request 完整枚举对比
## 最小使用方式

View File

@ -7,7 +7,7 @@ CQRS 迁移与收敛。
## 当前恢复点
- 恢复点编号:`CQRS-REWRITE-RP-106`
- 恢复点编号:`CQRS-REWRITE-RP-107`
- 当前阶段:`Phase 8`
- 当前 PR 锚点:`PR #340`
- 当前结论:
@ -43,20 +43,23 @@ CQRS 迁移与收敛。
- 当前 `RP-104` 已继续沿用 `$gframework-batch-boot 50` 压 request 热路径:先把 `CqrsDispatcher.SendAsync(...)` 改成 direct-return `ValueTask`,移除 dispatcher 自身的 `async/await` 状态机;再让 `MicrosoftDiContainer.HasRegistration(Type)` 在冻结后复用预构建的服务键索引,避免每次命中零 pipeline request 都线性扫描全部描述符;本轮 benchmark 表明第一刀显著压低 steady-state / lifetime request第二刀在当前短跑下主要确认“无回退、收益不明显”
- 当前 `RP-105` 已继续沿用 `$gframework-batch-boot 50` 压默认 request steady-state为 benchmark 最小宿主补齐 CQRS runtime / registrar / registration service 基础设施,让 `RequestBenchmarks` 不再只测反射路径,而是通过 handwritten generated registry + `RegisterCqrsHandlersFromAssembly(...)` 真实接上 generated request invoker provider本轮 benchmark 表明默认 request 路径进一步从约 `70.298 ns / 32 B` 压到约 `65.296 ns / 32 B``Singleton / Transient` lifetime 也同步收敛到约 `68.772 ns / 32 B``73.157 ns / 56 B`
- 当前 `RP-106` 已把同一套 generated-provider 宿主收口扩展到 `RequestPipelineBenchmarks`:新增 handwritten `GeneratedRequestPipelineBenchmarkRegistry`,并让 `RequestPipelineBenchmarks` 改走 `RegisterCqrsHandlersFromAssembly(...)` + benchmark CQRS 基础设施预接线;本轮 benchmark 表明 `0 pipeline` steady-state 进一步收敛到约 `64.755 ns / 32 B``1 pipeline``353.141 ns / 536 B``4 pipeline` 在短跑噪音下维持约 `555.083 ns / 896 B`
- `ai-plan` active 入口现以 `RP-106` 为最新恢复锚点;`PR #340``PR #339``PR #334``PR #331``PR #326``PR #323``PR #307` 与其他更早阶段细节均以下方归档或说明为准
- 当前 `RP-107` 已把默认 stream steady-state 宿主也切到 generated-provider 路径:新增 handwritten `GeneratedDefaultStreamingBenchmarkRegistry`,让 `StreamingBenchmarks` 改走 `RegisterCqrsHandlersFromAssembly(...)` 并在 setup/cleanup 清理 dispatcher cache同时将 `gframework-boot` / `gframework-batch-boot` 的默认停止规则改为“AI 上下文预算优先,建议在预计接近约 80% 安全上下文占用前收口”,不再把 changed files 误当作唯一阈值
- `ai-plan` active 入口现以 `RP-107` 为最新恢复锚点;`PR #340``PR #339``PR #334``PR #331``PR #326``PR #323``PR #307` 与其他更早阶段细节均以下方归档或说明为准
## 当前活跃事实
- 当前分支为 `feat/cqrs-optimization`
- 本轮 `$gframework-batch-boot 50``origin/main` (`4d6dbba6`, 2026-05-08 11:13:33 +0800) 为基线;本地 `main` 仍落后,不作为 branch diff 基线
- 当前已提交分支相对 `origin/main` 的累计 branch diff 为 `8 files / 358 lines`
- 本批待提交工作树集中在 `GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs``GFramework.Cqrs.Benchmarks/Messaging/GeneratedRequestPipelineBenchmarkRegistry.cs``GFramework.Cqrs.Benchmarks/README.md`,新增 generated-provider pipeline 宿主接线后仍明显低于 `$gframework-batch-boot 50` 的文件阈值
- 当前已提交分支相对 `origin/main` 的累计 branch diff 为 `10 files / 507 lines`
- 本批待提交工作树集中在 `GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs``GFramework.Cqrs.Benchmarks/Messaging/GeneratedDefaultStreamingBenchmarkRegistry.cs``GFramework.Cqrs.Benchmarks/README.md``.agents/skills/gframework-batch-boot/SKILL.md``.agents/skills/gframework-boot/SKILL.md`
- 当前批次后的默认停止依据已改为 AI 上下文预算:若下一轮预计会让活动对话、已加载 recovery 文档、验证输出与当前 diff 接近约 `80%` 安全上下文占用,应在当前自然批次边界停止,即使 branch diff 仍有余量
- `GFramework.Cqrs.Benchmarks` 作为 benchmark 基础设施项目,必须持续排除在 NuGet / GitHub Packages 发布集合之外
- `GFramework.Cqrs.Benchmarks` 现已覆盖 request steady-state、pipeline 数量矩阵、startup、request/stream generated invoker以及 request handler `Singleton / Transient` 生命周期矩阵
- `GFramework.Cqrs.Benchmarks` 当前以 NuGet 方式引用 `Mediator.Abstractions` / `Mediator.SourceGenerator` `3.0.2``ai-libs/Mediator` 只保留为本地源码/README 对照资料,不再参与 benchmark 项目编译
- 当前 request steady-state benchmark 已形成 baseline / `Mediator` / `MediatR` / `GFramework.Cqrs` 四方对照:最新约 `5.680 ns / 32 B`、`6.565 ns / 32 B``54.737 ns / 232 B``63.644 ns / 32 B`
- 当前 request lifetime benchmark 已继续收敛:`Singleton``GFramework.Cqrs` 最新约 `69.896 ns / 32 B``Transient` 下约 `72.880 ns / 56 B`;相较 `RP-104` 前的 `73.005 ns / 32 B``74.757 ns / 56 B` 已继续下降
- 当前 request steady-state benchmark 已形成 baseline / `Mediator` / `MediatR` / `GFramework.Cqrs` 四方对照:最新约 `5.608 ns / 32 B`、`5.445 ns / 32 B``57.071 ns / 232 B``64.825 ns / 32 B`
- 当前 request lifetime benchmark 已继续收敛:`Singleton``GFramework.Cqrs` 最新约 `69.275 ns / 32 B``Transient` 下约 `74.301 ns / 56 B`;相较 `RP-104` 前的 `73.005 ns / 32 B``74.757 ns / 56 B` 仍维持同一收敛区间
- 当前 request pipeline benchmark 已改为与默认 request steady-state 相同的 generated-provider 宿主接线路径:`0 pipeline``64.755 ns / 32 B``1 pipeline``353.141 ns / 536 B``4 pipeline``555.083 ns / 896 B`
- 当前 stream steady-state benchmark 也已切到 generated-provider 宿主接线路径baseline 约 `5.535 ns / 32 B``MediatR``59.499 ns / 232 B``GFramework.Cqrs``66.778 ns / 32 B`
- 本轮已验证旧 benchmark 劣化的两个主热点:`0 pipeline` 场景下仍解析空行为列表,以及容器查询热路径在 debug 禁用时仍构造日志字符串;两者收口后,`GFramework.Cqrs` request 路径不再出现额外数百字节分配
- `HasRegistration(Type)` 现在只把“同一服务键已注册”或“开放泛型服务键可闭合到目标类型”视为命中,不再把“仅以具体实现类型自注册”的行为误判为接口服务已注册;该语义与 `Get(Type)` / `GetAll(Type)` 已重新对齐
- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs` 已同步适配 `HasRegistration(Type)` fast-path避免 strict mock 因缺少新调用配置而在上下文失败语义断言前提前抛出 `Moq.MockException`
@ -64,7 +67,8 @@ CQRS 迁移与收敛。
- 当前 request steady-state 仍落后于 source-generated `Mediator``MediatR`,但差距已从“额外数百字节分配 + 近 300ns”收敛到“零 pipeline fast-path 仍慢约 `31ns` / `3.6x``Mediator`”;下一批若继续压 request dispatch应优先评估默认路径吸收 generated invoker/provider 的空间
- 本轮 `SendAsync(...)` 的 direct-return `ValueTask` 改动已证明确实是有效热点:同样的短跑配置下,`GFramework.Cqrs` steady-state request 从约 `83.823 ns` 下探到 `69-70 ns` 区间
- 冻结后 `HasRegistration(Type)` 服务键索引化在当前短跑下没有带来同等量级的可见收益,但也没有引入功能回退或额外分配;后续若继续压零 pipeline request应优先重新评估“默认 request 路径进一步吸收 generated invoker/provider”而不是继续堆叠同层级微优化
- 默认 `RequestBenchmarks``RequestPipelineBenchmarks` 现在都已通过 handwritten generated registry + 真实 `RegisterCqrsHandlersFromAssembly(...)` 宿主接线命中 generated request invoker provider不再只代表纯反射 request binding 路径
- 默认 `RequestBenchmarks``RequestPipelineBenchmarks``StreamingBenchmarks` 现在都已通过 handwritten generated registry + 真实 `RegisterCqrsHandlersFromAssembly(...)` 宿主接线命中 generated invoker provider不再只代表纯反射 binding 路径
- `gframework-boot``gframework-batch-boot` 现明确把“上下文预算接近约 80%”视为默认优先停止信号branch diff files / lines 仅保留为次级仓库范围指标
- 当前性能回归门槛已收紧为:只要改动触达 `GFramework.Cqrs` request dispatch、DI 热路径、invoker/provider、pipeline 或 benchmark 宿主,就必须至少复跑 `RequestBenchmarks.SendRequest_*``RequestLifetimeBenchmarks.SendRequest_*`
- 当前阶段的性能验收目标已明确为:默认 request steady-state 路径不要求超过 source-generated `Mediator`,但必须持续逼近它,并至少稳定快于基于反射 / 扫描的 `MediatR`
- `GFramework.Core` 当前已通过内部 bridge request / handler 把 legacy `ICommand``IAsyncCommand``IQuery``IAsyncQuery` 接到统一 `ICqrsRuntime`
@ -297,8 +301,8 @@ CQRS 迁移与收敛。
## 下一推荐步骤
1. 若继续沿用 `$gframework-batch-boot 50` 且优先处理性能,下一批先查看 `RequestLifetimeBenchmarks` 与 stream/notification 对照里是否还有未吸收 generated-provider 宿主收益的低风险切片
2. 若要把“至少超过反射版 `MediatR`”变成可执行目标,下一批应继续围绕 request dispatch / pipeline 路径的剩余常量开销下钻,并在每次改动后立即复跑 `RequestBenchmarks``RequestLifetimeBenchmarks``RequestPipelineBenchmarks`
1. 当前 turn 已接近默认的上下文预算停止线;本次提交后应停止,并在新的 turn 里从 `RP-107` 恢复点继续,而不是在本轮继续启动新的 benchmark 宿主或 runtime 热点切片
2. 若下一轮继续沿用 `$gframework-batch-boot` 且优先处理性能,先看 notification 或更高价值的 request dispatch 常量开销热点,而不是再机械按 changed files 追加小批次
3. 若 benchmark 对照需要继续贴近 `Mediator` 官方设计,再扩 `Mediator` 的 compile-time lifetime 或 stream 对照矩阵,而不是回头重试已被 benchmark 否决的 `GetAll(Type)` 零行为探测方案
## 活跃文档

View File

@ -2,6 +2,43 @@
## 2026-05-08
### 阶段:默认 stream benchmark 吸收 generated provider 宿主CQRS-REWRITE-RP-107
- 延续 `$gframework-batch-boot 50`但本轮按用户新增要求把默认停止依据改为“AI 上下文预算优先,建议在预计接近约 80% 安全上下文占用前收口”;在真正落代码前先复核:
- `origin/main` = `4d6dbba6`,提交时间 `2026-05-08 11:13:33 +0800`
- 当前分支 `feat/cqrs-optimization` 相对 `origin/main` 的累计 branch diff 为 `10 files / 507 lines`
- 当前 turn 已加载 `AGENTS.md``gframework-batch-boot` / `gframework-boot`、active tracking/trace、上一轮 benchmark 结果与多次 validation 输出,因此继续一个自然批次可以接受,但不应在本次提交后继续无界循环
- 本轮接受的只读探索结论:
- 默认 request / request pipeline 宿主都已吸收 generated provider`StreamingBenchmarks` 仍停在“直接注册单个 stream handler”的旧宿主路径口径与 `StreamInvokerBenchmarks` / 默认 request 组不对称
- 默认 stream steady-state 场景已经足够独立,适合用一份新的 handwritten generated stream registry 最小化收口,而不用再修改 runtime 语义
- 用户要求把停止条件从 changed files 改成 AI 上下文预算,因此 skill 文档本身也属于这一批必须一起落下的恢复边界更新
- 本轮主线程决策:
- 新增 `GeneratedDefaultStreamingBenchmarkRegistry`,用 handwritten generated registry + `ICqrsStreamInvokerProvider` + `IEnumeratesCqrsStreamInvokerDescriptors``StreamingBenchmarks.BenchmarkStreamRequest` 提供真实的 generated stream invoker descriptor
- 让 `StreamingBenchmarks` 改用 `RegisterCqrsHandlersFromAssembly(typeof(StreamingBenchmarks).Assembly)` 建容器,并在 `Setup/Cleanup` 前后显式清理 dispatcher 静态缓存
- 更新 `GFramework.Cqrs.Benchmarks/README.md`,明确默认 stream steady-state benchmark 也已接上 handwritten generated stream invoker provider
- 更新 `.agents/skills/gframework-batch-boot/SKILL.md``.agents/skills/gframework-boot/SKILL.md`,明确“上下文预算接近约 80% 时优先停止branch diff 文件/行数只作次级仓库范围信号”
- 本轮权威验证:
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs.Benchmarks/Messaging/GeneratedDefaultStreamingBenchmarkRegistry.cs GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs GFramework.Cqrs.Benchmarks/README.md`
- 结果:通过
- `git diff --check`
- 结果:通过
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
- 结果:通过
- 备注steady-state request 对照约为 baseline `5.608 ns / 32 B``Mediator` `5.445 ns / 32 B``MediatR` `57.071 ns / 232 B``GFramework.Cqrs` `64.825 ns / 32 B`
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestLifetimeBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
- 结果:通过
- 备注:`Singleton` 下 baseline / `MediatR` / `GFramework.Cqrs``4.446 ns / 51.331 ns / 69.275 ns``Transient` 下约 `4.918 ns / 56.382 ns / 74.301 ns`
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*StreamingBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
- 结果:通过
- 备注:默认 stream steady-state 对照约为 baseline `5.535 ns / 32 B``MediatR` `59.499 ns / 232 B``GFramework.Cqrs` `66.778 ns / 32 B`
- 本轮结论:
- 默认 stream steady-state benchmark 现在也已切到 generated-provider 宿主路径request / pipeline / stream 三个默认宿主场景的 benchmark 口径终于对齐
- `StreamingBenchmarks``GFramework.Cqrs` 结果约 `66.778 ns / 32 B`,仍慢于 `MediatR`,但没有新增分配或明显回退,说明这次宿主收口是低风险可接受的
- 更重要的是默认停止依据已从“branch diff 文件数是否触顶”改成“AI 上下文预算是否接近约 80%”;结合当前 turn 已加载的大量 recovery/validation/benchmark 输出,本次提交后应主动停止,而不是继续机械扩批
- 下一轮若继续性能线,应从 `RP-107` 恢复点重新进入,并优先挑选新的高价值热点族,而不是沿着当前 turn 再追加更多同类宿主收口
### 阶段request pipeline benchmark 吸收 generated provider 宿主CQRS-REWRITE-RP-106
- 延续 `$gframework-batch-boot 50`,本轮基于 `RP-105` 已验证的默认 request 宿主接线继续推进,并先复核 branch diff 基线: