feat(worker): clamp max-turns to a configurable ceiling
Runaway sessions were the single biggest cost driver: model_presets was never persisted (stayed code-only), default_max_turns shipped at 100, and ResolveMaxTurns had no upper bound, so a task/list override could run hundreds of turns unchecked. - TaskRunner.ResolveMaxTurns now clamps the resolved value to AppSettings.MaxTurnsCeiling (new column, default 80) and logs a warning with task id / requested / effective value when it clamps. - default_max_turns default lowered from 100 to 40 (entity, EF config, and the seeded row via the new AddMaxTurnsCeiling migration). - AppSettingsRepository.GetAsync backfills model_presets with the shipping defaults on first read instead of leaving the column null. - Settings > General's per-model preset table and the task/list agent editor now show a hint when a set max-turns value exceeds the ceiling.
This commit is contained in:
@@ -20,13 +20,49 @@ public class AppSettingsRepositoryTests : IDisposable
|
||||
|
||||
Assert.Equal(AppSettingsEntity.SingletonId, row.Id);
|
||||
Assert.Equal("sonnet", row.DefaultModel);
|
||||
Assert.Equal(100, row.DefaultMaxTurns);
|
||||
Assert.Equal(40, row.DefaultMaxTurns);
|
||||
Assert.Equal(80, row.MaxTurnsCeiling);
|
||||
Assert.Equal("auto", row.DefaultPermissionMode);
|
||||
Assert.Equal("sibling", row.WorktreeStrategy);
|
||||
Assert.Null(row.CentralWorktreeRoot);
|
||||
Assert.False(row.WorktreeAutoCleanupEnabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_Persists_MaxTurnsCeiling()
|
||||
{
|
||||
using (var ctx = _db.CreateContext())
|
||||
{
|
||||
var repo = new AppSettingsRepository(ctx);
|
||||
await repo.UpdateAsync(new AppSettingsEntity { MaxTurnsCeiling = 60 });
|
||||
}
|
||||
|
||||
using var readCtx = _db.CreateContext();
|
||||
var row = await new AppSettingsRepository(readCtx).GetAsync();
|
||||
Assert.Equal(60, row.MaxTurnsCeiling);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAsync_Backfills_Null_ModelPresets_With_Defaults()
|
||||
{
|
||||
using (var ctx = _db.CreateContext())
|
||||
{
|
||||
var repo = new AppSettingsRepository(ctx);
|
||||
// Force a row to exist with model_presets left null (the pre-upgrade state).
|
||||
await repo.UpdateAsync(new AppSettingsEntity());
|
||||
}
|
||||
|
||||
using var readCtx = _db.CreateContext();
|
||||
var row = await new AppSettingsRepository(readCtx).GetAsync();
|
||||
|
||||
Assert.NotNull(row.ModelPresets);
|
||||
Assert.Equal(ModelPresets.SerializeDefaults(), row.ModelPresets);
|
||||
|
||||
using var rereadCtx = _db.CreateContext();
|
||||
var reread = await new AppSettingsRepository(rereadCtx).GetAsync();
|
||||
Assert.Equal(ModelPresets.SerializeDefaults(), reread.ModelPresets);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_Persists_And_RoundTrips()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user