Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .agents/skills/gframework-batch-boot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -67,15 +76,25 @@ 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
- a timebox or validation budget is reached

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
Expand Down Expand Up @@ -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:
Expand All @@ -128,6 +148,7 @@ When shorthand is used:
- integrate or verify the result
- rerun the required validation
- 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
Expand Down Expand Up @@ -158,13 +179,15 @@ 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
- the branch has grown large enough that reviewability would materially degrade

When stopping, report:

- whether context budget was the deciding factor
- which baseline was used
- the exact metric value at stop time
- completed batches
Expand Down
12 changes: 9 additions & 3 deletions .agents/skills/gframework-boot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
67 changes: 67 additions & 0 deletions GFramework.Core/Ioc/MicrosoftDiContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ private sealed record VisibleServiceTypeGroup(Type ServiceType, int Count, int F
/// </summary>
private IServiceProvider? _provider;

/// <summary>
/// 冻结后可复用的服务类型可见性索引。
/// 容器冻结后注册集合不再变化,因此 <see cref="HasRegistration(Type)" /> 可以安全复用该索引。
/// </summary>
private FrozenServiceTypeIndex? _frozenServiceTypeIndex;

/// <summary>
/// 容器冻结状态标志,true表示容器已冻结不可修改
/// </summary>
Expand Down Expand Up @@ -1044,6 +1050,11 @@ public bool HasRegistration(Type type)
EnterReadLockOrThrowDisposed();
try
{
if (_frozenServiceTypeIndex is not null)
{
return _frozenServiceTypeIndex.Contains(type);
}

return HasRegistrationCore(type);
}
finally
Expand Down Expand Up @@ -1139,6 +1150,7 @@ public void Clear()
GetServicesUnsafe.Clear();
_registeredInstances.Clear();
_provider = null;
_frozenServiceTypeIndex = null;
_frozen = false;
_logger.Info("Container cleared");
}
Expand Down Expand Up @@ -1166,6 +1178,7 @@ public void Freeze()
}

_provider = GetServicesUnsafe.BuildServiceProvider();
_frozenServiceTypeIndex = FrozenServiceTypeIndex.Create(GetServicesUnsafe);
_frozen = true;
_logger.Info("IOC Container frozen - ServiceProvider built");
}
Expand All @@ -1175,6 +1188,59 @@ public void Freeze()
}
}

/// <summary>
/// 保存冻结后按服务键可见的精确服务类型与开放泛型定义集合。
/// </summary>
/// <remarks>
/// 该索引只回答“按当前服务键语义是否可见”,因此与 <see cref="Get(Type)" /> /
/// <see cref="GetAll(Type)" /> 一样不会退化为更宽松的可赋值匹配。
/// </remarks>
private sealed class FrozenServiceTypeIndex(HashSet<Type> exactServiceTypes, HashSet<Type> openGenericServiceTypes)
{
private readonly HashSet<Type> _exactServiceTypes = exactServiceTypes;
private readonly HashSet<Type> _openGenericServiceTypes = openGenericServiceTypes;

/// <summary>
/// 基于冻结时最终确定的服务描述符集合创建索引。
/// </summary>
/// <param name="descriptors">冻结时的服务描述符序列。</param>
/// <returns>供存在性判断热路径复用的服务键索引。</returns>
public static FrozenServiceTypeIndex Create(IEnumerable<ServiceDescriptor> descriptors)
{
ArgumentNullException.ThrowIfNull(descriptors);

var exactServiceTypes = new HashSet<Type>();
var openGenericServiceTypes = new HashSet<Type>();

foreach (var descriptor in descriptors)
{
var serviceType = descriptor.ServiceType;
exactServiceTypes.Add(serviceType);

if (serviceType.IsGenericTypeDefinition)
{
openGenericServiceTypes.Add(serviceType);
}
}

return new FrozenServiceTypeIndex(exactServiceTypes, openGenericServiceTypes);
}

/// <summary>
/// 判断当前索引是否声明了目标服务键。
/// </summary>
/// <param name="requestedType">要检查的服务类型。</param>
/// <returns>命中精确服务键或可闭合的开放泛型服务键时返回 <see langword="true" />。</returns>
public bool Contains(Type requestedType)
{
ArgumentNullException.ThrowIfNull(requestedType);

return _exactServiceTypes.Contains(requestedType) ||
requestedType.IsConstructedGenericType &&
_openGenericServiceTypes.Contains(requestedType.GetGenericTypeDefinition());
}
}

/// <summary>
/// 获取底层的服务集合
/// 提供对内部IServiceCollection的访问权限,用于高级配置和自定义操作
Expand Down Expand Up @@ -1250,6 +1316,7 @@ public void Dispose()
_disposed = true;
(_provider as IDisposable)?.Dispose();
_provider = null;
_frozenServiceTypeIndex = null;
GetServicesUnsafe.Clear();
_registeredInstances.Clear();
_frozen = false;
Expand Down
83 changes: 83 additions & 0 deletions GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@

using System;
using System.Linq;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Cqrs.Abstractions.Cqrs;
using GFramework.Cqrs.Internal;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using LegacyICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime;

namespace GFramework.Cqrs.Benchmarks.Messaging;

Expand All @@ -31,11 +34,91 @@ internal static MicrosoftDiContainer CreateFrozenGFrameworkContainer(Action<Micr
ArgumentNullException.ThrowIfNull(configure);

var container = new MicrosoftDiContainer();
RegisterCqrsInfrastructure(container);
configure(container);
container.Freeze();
return container;
}

/// <summary>
/// 为 benchmark 宿主补齐默认 CQRS runtime seam,确保它既能手工注册 handler,也能走真实的程序集注册入口。
/// </summary>
/// <param name="container">当前 benchmark 拥有的 GFramework 容器。</param>
/// <remarks>
/// `RegisterCqrsHandlersFromAssembly(...)` 依赖预先可见的 runtime / registrar / registration service 实例绑定。
/// benchmark 宿主直接使用裸 <see cref="MicrosoftDiContainer" />,因此需要在配置阶段先补齐这组基础设施,
/// 避免各个 benchmark 用例各自复制同一段前置接线逻辑。
/// </remarks>
private static void RegisterCqrsInfrastructure(MicrosoftDiContainer container)
{
ArgumentNullException.ThrowIfNull(container);

if (container.Get<ICqrsRuntime>() is null)
{
var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher");
var notificationPublisher = container.Get<GFramework.Cqrs.Notification.INotificationPublisher>();
var runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(container, runtimeLogger, notificationPublisher);
container.Register(runtime);
RegisterLegacyRuntimeAlias(container, runtime);
}
else if (container.Get<LegacyICqrsRuntime>() is null)
{
RegisterLegacyRuntimeAlias(container, container.GetRequired<ICqrsRuntime>());
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (container.Get<ICqrsHandlerRegistrar>() is null)
{
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar");
var registrar = GFramework.Cqrs.CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger);
container.Register<ICqrsHandlerRegistrar>(registrar);
}

if (container.Get<ICqrsRegistrationService>() is null)
{
var registrationLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsRegistrationService");
var registrar = container.GetRequired<ICqrsHandlerRegistrar>();
var registrationService = GFramework.Cqrs.CqrsRuntimeFactory.CreateRegistrationService(registrar, registrationLogger);
container.Register<ICqrsRegistrationService>(registrationService);
}
}

/// <summary>
/// 只激活当前 benchmark 场景明确拥有的 generated registry,避免同一程序集里的其他 benchmark registry
/// 扩大冻结后服务索引与 dispatcher descriptor 基线。
/// </summary>
/// <typeparam name="TRegistry">当前 benchmark 需要接入的 generated registry 类型。</typeparam>
/// <param name="container">承载 generated registry 注册结果的 GFramework benchmark 容器。</param>
internal static void RegisterGeneratedBenchmarkRegistry<TRegistry>(MicrosoftDiContainer container)
where TRegistry : class, GFramework.Cqrs.ICqrsHandlerRegistry
{
ArgumentNullException.ThrowIfNull(container);

var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar");
CqrsHandlerRegistrar.RegisterGeneratedRegistry(container, typeof(TRegistry), registrarLogger);
}

/// <summary>
/// 为旧命名空间下的 CQRS runtime 契约注册兼容别名。
/// </summary>
/// <param name="container">承载 runtime 别名的 benchmark 容器。</param>
/// <param name="runtime">当前正式 CQRS runtime 实例。</param>
/// <exception cref="InvalidOperationException">
/// <paramref name="runtime" /> 未同时实现 legacy CQRS runtime 契约。
/// </exception>
private static void RegisterLegacyRuntimeAlias(MicrosoftDiContainer container, ICqrsRuntime runtime)
{
ArgumentNullException.ThrowIfNull(container);
ArgumentNullException.ThrowIfNull(runtime);

if (runtime is not LegacyICqrsRuntime legacyRuntime)
{
throw new InvalidOperationException(
$"The registered {typeof(ICqrsRuntime).FullName} must also implement {typeof(LegacyICqrsRuntime).FullName}. Actual runtime type: {runtime.GetType().FullName}.");
}

container.Register<LegacyICqrsRuntime>(legacyRuntime);
}

/// <summary>
/// 创建只承载当前 benchmark handler 集合的最小 MediatR 宿主。
/// </summary>
Expand Down
Loading
Loading