feat: phase 4 slice 1/3 — the bible-building agent #52

Merged
bjoern merged 4 commits from feat/bible-agent into main 2026-08-13 06:23:57 +02:00
Member

Phase 4 opens (Bible building & translation → MVP). The plan: 1/3 the bible-building agent (this PR) · 2/3 the translation agent (sequential, EN glossary growth, page-summary precondition) · 3/3 the UI entries + the translation-view feedback→reprocess that makes the human gate whole (ADR 0019; pre-pixel-pass the gate is simply the end of the run).

The agent (ADR 0013, 0017)

  • BibleBuildingBlueprint — the grant is exactly the matrix row: view_page · list_regions · get_region plus list_bible and the bible writes (set_story_overview, add_glossary, upsert_character, upsert_lore, add_story_beat, set_page_summary). No region mutation, no metadata write, no ask_user — the reads and the upsert/list tools are the same instances the R&S agent uses, project-bound at construction.
  • New region reads (Agents/BibleBuilding/RegionReadTools.cs): unlike the annotation agents' page-bound variants, the bible agent walks the whole book — list_regions(page_number) and get_region(page_number, label) address pages by their 1-based reading-order number (the house rule: numbers, never Guids). Honest errors on a wrong page ("There is no page 9 — the project has N page(s)") and a wrong label (pointing at list_regions).
  • The prompt makes summaries the rhythm: one set_page_summary per page as it reads, because ADR 0013 makes the summary a hard precondition — the translation agent will refuse a page without one. Covers/credits get a one-liner; list_bible before adding; setup-seeded entries are refined, not duplicated.

The executor and entry (ADR 0018, 0019)

  • BibleBuildingExecutor — sequential, no page binding (PageId stays null). Deliberately alone: one reader grows one bible in one pass — that sequentiality is the consistency mechanism (ADR 0019's no-consistency-agent decision). Shared AgentAttemptSupport preparation; retry-with-distrust.
  • StartBibleRun — ready-only, start-or-join under the engine's single-flight gate. Annotation is not a hard gate: a partly-annotated book still yields summaries and cast; the disclosed trade is a thinner bible, not a refusal.

Tests (+7; 511 total)

The grant as a verbatim name-list; both region reads including the honest-error paths; a scripted agent that exercises its own grant end-to-end through the engine (reads the region's JP, grows the glossary, writes a page summary — asserted in the stores, plus roster-default model, cost on the row, PageId == null); the distrust retry; the missing-key Settings pointer; and the entry's draft guard + in-flight join + single planned stage. Class ran 3× in isolation, flake-free.

🤖 Generated with Claude Code

Phase 4 opens (Bible building & translation → MVP). The plan: **1/3 the bible-building agent** (this PR) · 2/3 the translation agent (sequential, EN glossary growth, page-summary precondition) · 3/3 the UI entries + the translation-view feedback→reprocess that makes the human gate whole (ADR 0019; pre-pixel-pass the gate is simply the end of the run). ## The agent (ADR 0013, 0017) - **`BibleBuildingBlueprint`** — the grant is exactly the matrix row: `view_page · list_regions · get_region` plus `list_bible` and the bible writes (`set_story_overview`, `add_glossary`, `upsert_character`, `upsert_lore`, `add_story_beat`, `set_page_summary`). No region mutation, no metadata write, no `ask_user` — the reads and the upsert/list tools are the same instances the R&S agent uses, project-bound at construction. - **New region reads** (`Agents/BibleBuilding/RegionReadTools.cs`): unlike the annotation agents' page-*bound* variants, the bible agent walks the whole book — `list_regions(page_number)` and `get_region(page_number, label)` address pages by their 1-based reading-order number (the house rule: numbers, never Guids). Honest errors on a wrong page ("There is no page 9 — the project has N page(s)") and a wrong label (pointing at `list_regions`). - **The prompt makes summaries the rhythm**: one `set_page_summary` per page *as it reads*, because ADR 0013 makes the summary a hard precondition — the translation agent will refuse a page without one. Covers/credits get a one-liner; `list_bible` before adding; setup-seeded entries are refined, not duplicated. ## The executor and entry (ADR 0018, 0019) - **`BibleBuildingExecutor`** — sequential, no page binding (`PageId` stays null). Deliberately alone: one reader grows one bible in one pass — that sequentiality *is* the consistency mechanism (ADR 0019's no-consistency-agent decision). Shared `AgentAttemptSupport` preparation; retry-with-distrust. - **`StartBibleRun`** — ready-only, start-or-join under the engine's single-flight gate. Annotation is *not* a hard gate: a partly-annotated book still yields summaries and cast; the disclosed trade is a thinner bible, not a refusal. ## Tests (+7; 511 total) The grant as a verbatim name-list; both region reads including the honest-error paths; a scripted agent that exercises its own grant end-to-end through the engine (reads the region's JP, grows the glossary, writes a page summary — asserted in the stores, plus roster-default model, cost on the row, `PageId == null`); the distrust retry; the missing-key Settings pointer; and the entry's draft guard + in-flight join + single planned stage. Class ran 3× in isolation, flake-free. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: phase 4 slice 1/3 — the bible-building agent
All checks were successful
CI / build (pull_request) Successful in 25s
CI / test (pull_request) Successful in 40s
1950ccbf24
The sequential pass that turns the annotated book into the translation
bible (ADR 0013, 0017). The blueprint holds exactly the matrix row:
page and region READS (view_page, the new project-scoped list_regions
and get_region, addressed by 1-based page number) plus the bible writes
(story overview, glossary, characters, lore, story beats, page
summaries) — no region mutation, no metadata, no ask_user. The prompt
makes page summaries the working rhythm: one per page as it reads,
because the translation agent will refuse a page without one.

The executor is deliberately alone — one reader grows one bible in one
pass; that sequentiality IS the consistency mechanism (ADR 0019's
no-consistency-agent decision). StartBibleRun is the ready-only
start-or-join entry under the engine's single-flight gate; annotation
is not a hard gate (a partly-annotated book still yields summaries and
cast).

511 tests: the grant as a name-list, both region reads with honest
wrong-page/wrong-label errors, a scripted agent exercising its own
grant end-to-end (read regions, grow glossary, write a summary), the
distrust retry, the missing-key pointer, and the entry's guard + join.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Summary

Summary
Generated on: 07/26/2026 - 11:58:51
Coverage date: 07/26/2026 - 11:58:34 - 07/26/2026 - 11:58:48
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 369
Files: 171
Line coverage: 94% (8858 of 9419)
Covered lines: 8858
Uncovered lines: 561
Coverable lines: 9419
Total lines: 17501
Branch coverage: 80.4% (1964 of 2440)
Covered branches: 1964
Total branches: 2440
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.9%
Name Line Branch
Orihon.BlazorAdapter 95.9% 88.4%
Orihon.BlazorAdapter.Bible.AddBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.AddCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.AddGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.AddLoreRowRequested 100%
Orihon.BlazorAdapter.Bible.BibleEffects 92.2% 79.1%
Orihon.BlazorAdapter.Bible.BibleLoaded 100%
Orihon.BlazorAdapter.Bible.BiblePage 93.7% 81.6%
Orihon.BlazorAdapter.Bible.BibleReducers 93.1%
Orihon.BlazorAdapter.Bible.BibleState 100%
Orihon.BlazorAdapter.Bible.BibleWriteFailed 100%
Orihon.BlazorAdapter.Bible.DeleteBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteLoreRowRequested 0%
Orihon.BlazorAdapter.Bible.LoadBible 100%
Orihon.BlazorAdapter.Bible.ReorderBeatsRequested 0%
Orihon.BlazorAdapter.Bible.SaveOverviewRequested 100%
Orihon.BlazorAdapter.Bible.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested 100%
Orihon.BlazorAdapter.BlazorAdapterAssembly 100%
Orihon.BlazorAdapter.Debounce 96.2% 94.4%
Orihon.BlazorAdapter.Diagnostics.CircuitError 100%
Orihon.BlazorAdapter.Diagnostics.CircuitErrorPanel 100%
Orihon.BlazorAdapter.Diagnostics.CircuitErrorSink 100% 85.7%
Orihon.BlazorAdapter.Diagnostics.OrihonStoreInitializer 85.7% 66.6%
Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.DeletePageSummaryRequested 100%
Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace 100%
Orihon.BlazorAdapter.PageWorkspace.PageSummarySaved 100%
Orihon.BlazorAdapter.PageWorkspace.PageViewport 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage 91.8% 85.1%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers 100% 66.6%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceState 100%
Orihon.BlazorAdapter.PageWorkspace.PageWriteFailed 100%
Orihon.BlazorAdapter.PageWorkspace.RegionCreated 100%
Orihon.BlazorAdapter.PageWorkspace.RegionSaved 100%
Orihon.BlazorAdapter.PageWorkspace.ReorderRegionsRequested 100%
Orihon.BlazorAdapter.PageWorkspace.ReprocessPageRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SavePageSummaryRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested 100%
Orihon.BlazorAdapter.Projects.CreateProjectRequested 100%
Orihon.BlazorAdapter.Projects.DecideSetupContinuation 100%
Orihon.BlazorAdapter.Projects.DeleteProjectRequested 100%
Orihon.BlazorAdapter.Projects.FinishSetupRequested 100%
Orihon.BlazorAdapter.Projects.ImportPagesRequested 100%
Orihon.BlazorAdapter.Projects.LoadWizard 100%
Orihon.BlazorAdapter.Projects.PagesImported 100%
Orihon.BlazorAdapter.Projects.ProjectDeleteFailed 100%
Orihon.BlazorAdapter.Projects.ProjectListEffects 100% 100%
Orihon.BlazorAdapter.Projects.ProjectListPage 89.7% 91.1%
Orihon.BlazorAdapter.Projects.ProjectListReducers 100%
Orihon.BlazorAdapter.Projects.ProjectListState 100%
Orihon.BlazorAdapter.Projects.ProjectsLoaded 100%
Orihon.BlazorAdapter.Projects.ProjectWizardEffects 100% 100%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 94.1% 86.2%
Orihon.BlazorAdapter.Projects.ProjectWizardReducers 100%
Orihon.BlazorAdapter.Projects.ProjectWizardState 100%
Orihon.BlazorAdapter.Projects.SetupChat 93.5% 100%
Orihon.BlazorAdapter.Projects.SetupChatEffects 100% 100%
Orihon.BlazorAdapter.Projects.SetupChatFailed 100%
Orihon.BlazorAdapter.Projects.SetupChatReducers 100%
Orihon.BlazorAdapter.Projects.SetupChatState 100%
Orihon.BlazorAdapter.Projects.SetupChatUpdated 100%
Orihon.BlazorAdapter.Projects.StartSetupChat 100%
Orihon.BlazorAdapter.Projects.SubmitSetupAnswer 100%
Orihon.BlazorAdapter.Projects.WizardLoaded 100%
Orihon.BlazorAdapter.Projects.WizardWriteFailed 100%
Orihon.BlazorAdapter.Runs.MonitorRunLoaded 100%
Orihon.BlazorAdapter.Runs.RunChangedBridge 94.1% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitor 100% 97.6%
Orihon.BlazorAdapter.Runs.RunMonitorEffects 100% 100%
Orihon.BlazorAdapter.Runs.RunMonitorReducers 100%
Orihon.BlazorAdapter.Runs.RunMonitorState 100%
Orihon.BlazorAdapter.Settings.AgentModelPicked 100%
Orihon.BlazorAdapter.Settings.AgentModelSaved 100%
Orihon.BlazorAdapter.Settings.AgentModelSaveFailed 100%
Orihon.BlazorAdapter.Settings.KeySaved 100%
Orihon.BlazorAdapter.Settings.KeySaveFailed 100%
Orihon.BlazorAdapter.Settings.ModelOptionsLoaded 100%
Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable 100%
Orihon.BlazorAdapter.Settings.SaveKeyRequested 100%
Orihon.BlazorAdapter.Settings.SettingsEffects 100% 100%
Orihon.BlazorAdapter.Settings.SettingsLoaded 100%
Orihon.BlazorAdapter.Settings.SettingsPage 100% 90.4%
Orihon.BlazorAdapter.Settings.SettingsReducers 100%
Orihon.BlazorAdapter.Settings.SettingsState 100%
Orihon.BlazorAdapter.Uploads.UploadTransfer 96.5% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferProgress 100% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferResult 100%
Orihon.BlazorAdapter.Workspace.CreateChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeletePageRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace 100%
Orihon.BlazorAdapter.Workspace.MovePageRequested 100%
Orihon.BlazorAdapter.Workspace.ProjectMetadataCard 95.2% 92.8%
Orihon.BlazorAdapter.Workspace.ProjectMetadataSaved 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage 95.5% 88.3%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers 100% 62.5%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState 100%
Orihon.BlazorAdapter.Workspace.RenameChapterRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderPagesRequested 100%
Orihon.BlazorAdapter.Workspace.RunAnnotationRequested 100%
Orihon.BlazorAdapter.Workspace.SaveProjectMetadataRequested 100%
Orihon.BlazorAdapter.Workspace.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.SetPageKindRequested 100%
Orihon.BlazorAdapter.Workspace.SummaryDeleted 100%
Orihon.BlazorAdapter.Workspace.SummarySaved 100%
Orihon.BlazorAdapter.Workspace.WorkspaceImportRequested 100%
Orihon.BlazorAdapter.Workspace.WorkspaceWriteFailed 100%
Orihon.Domain - 100%
Name Line Branch
Orihon.Domain 100% 100%
Orihon.Domain.Agents.AgentDescriptor 100%
Orihon.Domain.Agents.AgentRoster 100% 100%
Orihon.Domain.Bible.Character 100% 100%
Orihon.Domain.Bible.GlossaryEntry 100% 100%
Orihon.Domain.Bible.LoreEntry 100% 100%
Orihon.Domain.Bible.PageSummary 100%
Orihon.Domain.Bible.StoryBeat 100%
Orihon.Domain.Bible.StoryOverview 100%
Orihon.Domain.Projects.Project 100% 100%
Orihon.Domain.Projects.ProjectProfile 100%
Orihon.Domain.Runs.Execution 100% 100%
Orihon.Domain.Runs.Run 100%
Orihon.Domain.Settings.AppSetting 100%
Orihon.Domain.Text 100% 100%
Orihon.Domain.Translation.BoundingBox 100%
Orihon.Domain.Translation.Chapter 100%
Orihon.Domain.Translation.Page 100%
Orihon.Domain.Translation.Region 100% 100%
Orihon.Domain.Translation.RegionProfile 100%
Orihon.Infrastructure - 94.3%
Name Line Branch
Orihon.Infrastructure 94.3% 67.3%
Orihon.Infrastructure.Bible.EfBibleStore 94.4% 91.6%
Orihon.Infrastructure.DependencyInjection 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter`1 100% 100%
Orihon.Infrastructure.Gateways.HttpWebPageFetcher 95.1% 83.3%
Orihon.Infrastructure.Gateways.OpenRouterLlmGateway 98% 83%
Orihon.Infrastructure.Gateways.SkiaPageImageRenderer 96.6% 86.1%
Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper 100%
Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RunConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryOverviewConfiguration 100%
Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Orihon.Infrastructure.Persistence.Migrations.AddAppSettings 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddRuns 99.1%
Orihon.Infrastructure.Persistence.Migrations.AddStoryOverview 99.5%
Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain 97.3%
Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot 100%
Orihon.Infrastructure.Persistence.OrihonDbContext 100%
Orihon.Infrastructure.Persistence.OrihonDbContextFactory 100%
Orihon.Infrastructure.Projects.EfProjectStore 100% 100%
Orihon.Infrastructure.Projects.FileSystemPageImageStore 100% 100%
Orihon.Infrastructure.Runs.EfRunStore 97% 50%
Orihon.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Orihon.Infrastructure.Translation.EfChapterStore 100% 100%
Orihon.Infrastructure.Translation.EfPageStore 86% 80%
Orihon.Infrastructure.Translation.EfRegionStore 100% 100%
Orihon.Infrastructure.Translation.Ordering 100% 100%
System.Text.RegularExpressions.Generated 70.6% 53.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
77.9% 76.6%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
59% 42.5%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
89.4% 75%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
83.7% 62.5%
Orihon.Kernel - 90.9%
Name Line Branch
Orihon.Kernel 90.9% 75%
Orihon.Kernel.Err`1 100%
Orihon.Kernel.Ok`1 100%
Orihon.Kernel.Result`1 88.8% 75%
Orihon.Server - 93.4%
Name Line Branch
Orihon.Server 93.4% 68.4%
Orihon.Server.Components.App 100%
Orihon.Server.Components.Layout.MainLayout 100%
Orihon.Server.Components.Pages.Gate 64.2% 66.6%
Orihon.Server.RunEngineBootstrap 100%
Orihon.Server.Security.AccessGate 91.8% 41.6%
Orihon.Server.Security.AccessSecret 100% 50%
Orihon.Server.VolumeStartupValidator 100% 100%
Program 95.4% 85.7%
Orihon.UseCases - 91.1%
Name Line Branch
Orihon.UseCases 91.1% 83%
Orihon.UseCases.Agents.AgentAttemptPreparation 100%
Orihon.UseCases.Agents.AgentAttemptSupport 100% 93.7%
Orihon.UseCases.Agents.AgentBlueprint 100%
Orihon.UseCases.Agents.AgentInvocation 100%
Orihon.UseCases.Agents.AgentOutcome 100%
Orihon.UseCases.Agents.AgentTool`1 90.9% 75%
Orihon.UseCases.Agents.AgentToolImage 100%
Orihon.UseCases.Agents.AgentToolResult 100%
Orihon.UseCases.Agents.Annotation.AddRegionParams 100%
Orihon.UseCases.Agents.Annotation.AddRegionTool 76.9% 50%
Orihon.UseCases.Agents.Annotation.AnnotationBlueprints 100%
Orihon.UseCases.Agents.Annotation.AnnotationStage 94.7% 50%
Orihon.UseCases.Agents.Annotation.BboxCreationExecutor 94.1% 50%
Orihon.UseCases.Agents.Annotation.BboxRefinementExecutor 90.4% 62.5%
Orihon.UseCases.Agents.Annotation.BoundBoxParams 0%
Orihon.UseCases.Agents.Annotation.BoundContactSheetParams 0%
Orihon.UseCases.Agents.Annotation.BoundContactSheetTool 10.7% 0%
Orihon.UseCases.Agents.Annotation.BoundCropParams 0%
Orihon.UseCases.Agents.Annotation.BoundCropTool 42.8%
Orihon.UseCases.Agents.Annotation.BoundViewAnnotatedTool 15% 0%
Orihon.UseCases.Agents.Annotation.BoundViewPageTool 18.7% 0%
Orihon.UseCases.Agents.Annotation.BoundViewParams 0%
Orihon.UseCases.Agents.Annotation.BoundZoomParams 0%
Orihon.UseCases.Agents.Annotation.BoundZoomTool 37.5%
Orihon.UseCases.Agents.Annotation.DeleteBoundRegionTool 91.6% 100%
Orihon.UseCases.Agents.Annotation.DeleteRegionParams 100%
Orihon.UseCases.Agents.Annotation.DeleteRegionTool 85.7% 100%
Orihon.UseCases.Agents.Annotation.FindGlossaryParams 100%
Orihon.UseCases.Agents.Annotation.FindGlossaryTool 76.4% 62.5%
Orihon.UseCases.Agents.Annotation.ListRegionsTool 76.4% 60%
Orihon.UseCases.Agents.Annotation.MoveResizeBoundTool 27.2% 0%
Orihon.UseCases.Agents.Annotation.MoveResizeRegionParams 100%
Orihon.UseCases.Agents.Annotation.MoveResizeRegionTool 73.3% 50%
Orihon.UseCases.Agents.Annotation.PageQaExecutor 94.4% 83.3%
Orihon.UseCases.Agents.Annotation.QaReportSink 100%
Orihon.UseCases.Agents.Annotation.RegionAuthoringAccess 86.6% 53.8%
Orihon.UseCases.Agents.Annotation.RejectRegionParams 100%
Orihon.UseCases.Agents.Annotation.RejectRegionTool 85.7% 50%
Orihon.UseCases.Agents.Annotation.ReorderRegionParams 100%
Orihon.UseCases.Agents.Annotation.ReorderRegionTool 80% 60%
Orihon.UseCases.Agents.Annotation.ReportQaParams 100%
Orihon.UseCases.Agents.Annotation.ReportQaTool 82.3% 93.7%
Orihon.UseCases.Agents.Annotation.SetPageMetaParams 100%
Orihon.UseCases.Agents.Annotation.SetPageMetaTool 85.7% 75%
Orihon.UseCases.Agents.Annotation.SetRegionTypeParams 100%
Orihon.UseCases.Agents.Annotation.SetRegionTypeTool 85.7% 87.5%
Orihon.UseCases.Agents.Annotation.SetTranscriptionParams 100%
Orihon.UseCases.Agents.Annotation.SetTranscriptionTool 80% 100%
Orihon.UseCases.Agents.Annotation.TranscriptionExecutor 90.4% 75%
Orihon.UseCases.Agents.AssistantSpoke 100%
Orihon.UseCases.Agents.BibleBuilding.BibleBuildingBlueprint 100%
Orihon.UseCases.Agents.BibleBuilding.BibleBuildingExecutor 95.4% 75%
Orihon.UseCases.Agents.BibleBuilding.GetRegionParams 100%
Orihon.UseCases.Agents.BibleBuilding.GetRegionTool 84.6% 72.2%
Orihon.UseCases.Agents.BibleBuilding.ListProjectRegionsTool 86.3% 90%
Orihon.UseCases.Agents.BibleBuilding.ListRegionsParams 100%
Orihon.UseCases.Agents.Inspection.ContactSheetParams 100%
Orihon.UseCases.Agents.Inspection.ContactSheetTool 82.1% 92.8%
Orihon.UseCases.Agents.Inspection.CropParams 100%
Orihon.UseCases.Agents.Inspection.CropTool 42.8%
Orihon.UseCases.Agents.Inspection.PageImageAccess 66.6% 62%
Orihon.UseCases.Agents.Inspection.ViewAnnotatedParams 100%
Orihon.UseCases.Agents.Inspection.ViewAnnotatedTool 76.1% 83.3%
Orihon.UseCases.Agents.Inspection.ZoomParams 100%
Orihon.UseCases.Agents.Inspection.ZoomTool 44.4%
Orihon.UseCases.Agents.ResearchSetup.AddGlossaryParams 100%
Orihon.UseCases.Agents.ResearchSetup.AddGlossaryTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatParams 100%
Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatTool 100% 50%
Orihon.UseCases.Agents.ResearchSetup.AskUserParams 100%
Orihon.UseCases.Agents.ResearchSetup.AskUserTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.FetchUrlParams 100%
Orihon.UseCases.Agents.ResearchSetup.FetchUrlTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.ListBibleTool 89.4% 100%
Orihon.UseCases.Agents.ResearchSetup.PageByNumber 90% 87.5%
Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageMetaParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageMetaTool 95.2% 90%
Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryTool 100% 75%
Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataTool 96.1% 90.9%
Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool 91.3% 66.6%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool 91.3% 66.6%
Orihon.UseCases.Agents.ResearchSetup.ViewPageParams 100%
Orihon.UseCases.Agents.ResearchSetup.ViewPageTool 100% 100%
Orihon.UseCases.Agents.Setup.ResearchSetupExecutor 97.2% 89.2%
Orihon.UseCases.Agents.Setup.SetupChatEntry 100%
Orihon.UseCases.Agents.Setup.SetupConversation 100% 87.5%
Orihon.UseCases.Agents.Setup.SetupConversationRegistry 100%
Orihon.UseCases.Agents.ToolCalled 100%
Orihon.UseCases.Agents.ToolCompleted 100%
Orihon.UseCases.Bible.AddCharacter 100% 100%
Orihon.UseCases.Bible.AddGlossaryEntry 100% 100%
Orihon.UseCases.Bible.AddLoreEntry 100% 100%
Orihon.UseCases.Bible.AddStoryBeat 100% 100%
Orihon.UseCases.Bible.BibleDto 100%
Orihon.UseCases.Bible.CharacterDto 100%
Orihon.UseCases.Bible.DeleteCharacter 100% 100%
Orihon.UseCases.Bible.DeleteGlossaryEntry 100% 100%
Orihon.UseCases.Bible.DeleteLoreEntry 100% 100%
Orihon.UseCases.Bible.DeletePageSummary 100% 100%
Orihon.UseCases.Bible.DeleteStoryBeat 100% 100%
Orihon.UseCases.Bible.GetBible 100% 100%
Orihon.UseCases.Bible.GlossaryEntryDto 100%
Orihon.UseCases.Bible.LoreEntryDto 100%
Orihon.UseCases.Bible.PageSummaryDto 100%
Orihon.UseCases.Bible.ReorderStoryBeats 100%
Orihon.UseCases.Bible.SetPageSummary 100% 100%
Orihon.UseCases.Bible.SetStoryOverview 100% 100%
Orihon.UseCases.Bible.StoryBeatDto 100%
Orihon.UseCases.Bible.StoryOverviewDto 100%
Orihon.UseCases.Bible.UpdateCharacter 100% 100%
Orihon.UseCases.Bible.UpdateGlossaryEntry 100% 100%
Orihon.UseCases.Bible.UpdateLoreEntry 100% 100%
Orihon.UseCases.Bible.UpdateStoryBeat 100% 100%
Orihon.UseCases.Chapters.ChapterDto 100%
Orihon.UseCases.Chapters.CreateChapter 100% 100%
Orihon.UseCases.Chapters.DeleteChapter 100% 100%
Orihon.UseCases.Chapters.RenameChapter 100% 100%
Orihon.UseCases.Chapters.ReorderChapters 100%
Orihon.UseCases.DependencyInjection 100%
Orihon.UseCases.Diagnostics.SeedDevData 99.2% 92.8%
Orihon.UseCases.Gateways.LabeledBox 100%
Orihon.UseCases.Gateways.LlmKeyInfo 100%
Orihon.UseCases.Gateways.LlmModel 100%
Orihon.UseCases.NextOrder 100%
Orihon.UseCases.Pages.DeletePage 100% 100%
Orihon.UseCases.Pages.GetPage 100% 100%
Orihon.UseCases.Pages.GetProjectWorkspace 100% 100%
Orihon.UseCases.Pages.ImportPages 100% 100%
Orihon.UseCases.Pages.ImportPagesResult 100%
Orihon.UseCases.Pages.MarkPageAnnotated 100% 100%
Orihon.UseCases.Pages.MovePage 100% 92.8%
Orihon.UseCases.Pages.PageDetailDto 100%
Orihon.UseCases.Pages.PageDto 100%
Orihon.UseCases.Pages.PageUpload 100%
Orihon.UseCases.Pages.ProjectWorkspaceDto 100%
Orihon.UseCases.Pages.ReorderPages 100%
Orihon.UseCases.Pages.SetPageMeta 100% 100%
Orihon.UseCases.Pages.WorkspaceChapterDto 100%
Orihon.UseCases.Projects.CompleteProjectSetup 100% 93.7%
Orihon.UseCases.Projects.CreateProject 100% 100%
Orihon.UseCases.Projects.DeleteProject 100% 100%
Orihon.UseCases.Projects.GetProject 100% 100%
Orihon.UseCases.Projects.ListProjects 100%
Orihon.UseCases.Projects.ProjectDto 95.8%
Orihon.UseCases.Projects.StartAnnotationRun 95.4% 90%
Orihon.UseCases.Projects.StartBibleRun 90.9% 83.3%
Orihon.UseCases.Projects.StartSetupRun 100% 100%
Orihon.UseCases.Projects.StoredPageImage 100%
Orihon.UseCases.Projects.UpdateProjectMetadata 100% 100%
Orihon.UseCases.Regions.CreateRegion 100% 100%
Orihon.UseCases.Regions.DeleteRegion 100% 100%
Orihon.UseCases.Regions.RegionDto 97%
Orihon.UseCases.Regions.ReorderRegions 100%
Orihon.UseCases.Regions.UpdateRegion 100% 100%
Orihon.UseCases.Runs.AnnotationPipeline 100% 100%
Orihon.UseCases.Runs.ExecutionDto 92.3%
Orihon.UseCases.Runs.PlannedExecution 100%
Orihon.UseCases.Runs.ReprocessPage 100% 94.4%
Orihon.UseCases.Runs.RunDto 93.3% 90%
Orihon.UseCases.Runs.RunEngine 95.3% 90.6%
Orihon.UseCases.Runs.RunEngineOptions 100%
Orihon.UseCases.Runs.StageContext 87.5%
Orihon.UseCases.Runs.StageHaltedException 100%
Orihon.UseCases.Settings.AgentSettingDto 100% 100%
Orihon.UseCases.Settings.GetSettings 100% 100%
Orihon.UseCases.Settings.ListModelOptions 100% 100%
Orihon.UseCases.Settings.SaveAgentModel 100% 100%
Orihon.UseCases.Settings.SaveOpenRouterKey 100% 100%
Orihon.UseCases.Settings.SettingKeys 100% 100%
Orihon.UseCases.Settings.SettingsDto 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/26/2026 - 11:58:51 | | Coverage date: | 07/26/2026 - 11:58:34 - 07/26/2026 - 11:58:48 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 369 | | Files: | 171 | | **Line coverage:** | 94% (8858 of 9419) | | Covered lines: | 8858 | | Uncovered lines: | 561 | | Coverable lines: | 9419 | | Total lines: | 17501 | | **Branch coverage:** | 80.4% (1964 of 2440) | | Covered branches: | 1964 | | Total branches: | 2440 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 95.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**95.9%**|**88.4%**| |Orihon.BlazorAdapter.Bible.AddBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddLoreRowRequested|100%|| |Orihon.BlazorAdapter.Bible.BibleEffects|92.2%|79.1%| |Orihon.BlazorAdapter.Bible.BibleLoaded|100%|| |Orihon.BlazorAdapter.Bible.BiblePage|93.7%|81.6%| |Orihon.BlazorAdapter.Bible.BibleReducers|93.1%|| |Orihon.BlazorAdapter.Bible.BibleState|100%|| |Orihon.BlazorAdapter.Bible.BibleWriteFailed|100%|| |Orihon.BlazorAdapter.Bible.DeleteBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteLoreRowRequested|0%|| |Orihon.BlazorAdapter.Bible.LoadBible|100%|| |Orihon.BlazorAdapter.Bible.ReorderBeatsRequested|0%|| |Orihon.BlazorAdapter.Bible.SaveOverviewRequested|100%|| |Orihon.BlazorAdapter.Bible.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested|100%|| |Orihon.BlazorAdapter.BlazorAdapterAssembly|100%|| |Orihon.BlazorAdapter.Debounce|96.2%|94.4%| |Orihon.BlazorAdapter.Diagnostics.CircuitError|100%|| |Orihon.BlazorAdapter.Diagnostics.CircuitErrorPanel|100%|| |Orihon.BlazorAdapter.Diagnostics.CircuitErrorSink|100%|85.7%| |Orihon.BlazorAdapter.Diagnostics.OrihonStoreInitializer|85.7%|66.6%| |Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.DeletePageSummaryRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageSummarySaved|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageViewport|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage|91.8%|85.1%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers|100%|66.6%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceState|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWriteFailed|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionCreated|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionSaved|100%|| |Orihon.BlazorAdapter.PageWorkspace.ReorderRegionsRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.ReprocessPageRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SavePageSummaryRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested|100%|| |Orihon.BlazorAdapter.Projects.CreateProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.DecideSetupContinuation|100%|| |Orihon.BlazorAdapter.Projects.DeleteProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.FinishSetupRequested|100%|| |Orihon.BlazorAdapter.Projects.ImportPagesRequested|100%|| |Orihon.BlazorAdapter.Projects.LoadWizard|100%|| |Orihon.BlazorAdapter.Projects.PagesImported|100%|| |Orihon.BlazorAdapter.Projects.ProjectDeleteFailed|100%|| |Orihon.BlazorAdapter.Projects.ProjectListEffects|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectListPage|89.7%|91.1%| |Orihon.BlazorAdapter.Projects.ProjectListReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectListState|100%|| |Orihon.BlazorAdapter.Projects.ProjectsLoaded|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardEffects|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|94.1%|86.2%| |Orihon.BlazorAdapter.Projects.ProjectWizardReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardState|100%|| |Orihon.BlazorAdapter.Projects.SetupChat|93.5%|100%| |Orihon.BlazorAdapter.Projects.SetupChatEffects|100%|100%| |Orihon.BlazorAdapter.Projects.SetupChatFailed|100%|| |Orihon.BlazorAdapter.Projects.SetupChatReducers|100%|| |Orihon.BlazorAdapter.Projects.SetupChatState|100%|| |Orihon.BlazorAdapter.Projects.SetupChatUpdated|100%|| |Orihon.BlazorAdapter.Projects.StartSetupChat|100%|| |Orihon.BlazorAdapter.Projects.SubmitSetupAnswer|100%|| |Orihon.BlazorAdapter.Projects.WizardLoaded|100%|| |Orihon.BlazorAdapter.Projects.WizardWriteFailed|100%|| |Orihon.BlazorAdapter.Runs.MonitorRunLoaded|100%|| |Orihon.BlazorAdapter.Runs.RunChangedBridge|94.1%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitor|100%|97.6%| |Orihon.BlazorAdapter.Runs.RunMonitorEffects|100%|100%| |Orihon.BlazorAdapter.Runs.RunMonitorReducers|100%|| |Orihon.BlazorAdapter.Runs.RunMonitorState|100%|| |Orihon.BlazorAdapter.Settings.AgentModelPicked|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaved|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaveFailed|100%|| |Orihon.BlazorAdapter.Settings.KeySaved|100%|| |Orihon.BlazorAdapter.Settings.KeySaveFailed|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsLoaded|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable|100%|| |Orihon.BlazorAdapter.Settings.SaveKeyRequested|100%|| |Orihon.BlazorAdapter.Settings.SettingsEffects|100%|100%| |Orihon.BlazorAdapter.Settings.SettingsLoaded|100%|| |Orihon.BlazorAdapter.Settings.SettingsPage|100%|90.4%| |Orihon.BlazorAdapter.Settings.SettingsReducers|100%|| |Orihon.BlazorAdapter.Settings.SettingsState|100%|| |Orihon.BlazorAdapter.Uploads.UploadTransfer|96.5%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferProgress|100%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferResult|100%|| |Orihon.BlazorAdapter.Workspace.CreateChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeletePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace|100%|| |Orihon.BlazorAdapter.Workspace.MovePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.ProjectMetadataCard|95.2%|92.8%| |Orihon.BlazorAdapter.Workspace.ProjectMetadataSaved|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage|95.5%|88.3%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers|100%|62.5%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState|100%|| |Orihon.BlazorAdapter.Workspace.RenameChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderPagesRequested|100%|| |Orihon.BlazorAdapter.Workspace.RunAnnotationRequested|100%|| |Orihon.BlazorAdapter.Workspace.SaveProjectMetadataRequested|100%|| |Orihon.BlazorAdapter.Workspace.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.SetPageKindRequested|100%|| |Orihon.BlazorAdapter.Workspace.SummaryDeleted|100%|| |Orihon.BlazorAdapter.Workspace.SummarySaved|100%|| |Orihon.BlazorAdapter.Workspace.WorkspaceImportRequested|100%|| |Orihon.BlazorAdapter.Workspace.WorkspaceWriteFailed|100%|| </details> <details><summary>Orihon.Domain - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Domain**|**100%**|**100%**| |Orihon.Domain.Agents.AgentDescriptor|100%|| |Orihon.Domain.Agents.AgentRoster|100%|100%| |Orihon.Domain.Bible.Character|100%|100%| |Orihon.Domain.Bible.GlossaryEntry|100%|100%| |Orihon.Domain.Bible.LoreEntry|100%|100%| |Orihon.Domain.Bible.PageSummary|100%|| |Orihon.Domain.Bible.StoryBeat|100%|| |Orihon.Domain.Bible.StoryOverview|100%|| |Orihon.Domain.Projects.Project|100%|100%| |Orihon.Domain.Projects.ProjectProfile|100%|| |Orihon.Domain.Runs.Execution|100%|100%| |Orihon.Domain.Runs.Run|100%|| |Orihon.Domain.Settings.AppSetting|100%|| |Orihon.Domain.Text|100%|100%| |Orihon.Domain.Translation.BoundingBox|100%|| |Orihon.Domain.Translation.Chapter|100%|| |Orihon.Domain.Translation.Page|100%|| |Orihon.Domain.Translation.Region|100%|100%| |Orihon.Domain.Translation.RegionProfile|100%|| </details> <details><summary>Orihon.Infrastructure - 94.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**94.3%**|**67.3%**| |Orihon.Infrastructure.Bible.EfBibleStore|94.4%|91.6%| |Orihon.Infrastructure.DependencyInjection|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter`1|100%|100%| |Orihon.Infrastructure.Gateways.HttpWebPageFetcher|95.1%|83.3%| |Orihon.Infrastructure.Gateways.OpenRouterLlmGateway|98%|83%| |Orihon.Infrastructure.Gateways.SkiaPageImageRenderer|96.6%|86.1%| |Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper|100%|| |Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RunConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryOverviewConfiguration|100%|| |Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Orihon.Infrastructure.Persistence.Migrations.AddAppSettings|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddRuns|99.1%|| |Orihon.Infrastructure.Persistence.Migrations.AddStoryOverview|99.5%|| |Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain|97.3%|| |Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContext|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContextFactory|100%|| |Orihon.Infrastructure.Projects.EfProjectStore|100%|100%| |Orihon.Infrastructure.Projects.FileSystemPageImageStore|100%|100%| |Orihon.Infrastructure.Runs.EfRunStore|97%|50%| |Orihon.Infrastructure.Settings.EfAppSettingsStore|100%|100%| |Orihon.Infrastructure.Translation.EfChapterStore|100%|100%| |Orihon.Infrastructure.Translation.EfPageStore|86%|80%| |Orihon.Infrastructure.Translation.EfRegionStore|100%|100%| |Orihon.Infrastructure.Translation.Ordering|100%|100%| |System.Text.RegularExpressions.Generated|70.6%|53.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4|77.9%|76.6%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1|59%|42.5%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3|89.4%|75%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2|83.7%|62.5%| </details> <details><summary>Orihon.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Kernel**|**90.9%**|**75%**| |Orihon.Kernel.Err`1|100%|| |Orihon.Kernel.Ok`1|100%|| |Orihon.Kernel.Result`1|88.8%|75%| </details> <details><summary>Orihon.Server - 93.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**93.4%**|**68.4%**| |Orihon.Server.Components.App|100%|| |Orihon.Server.Components.Layout.MainLayout|100%|| |Orihon.Server.Components.Pages.Gate|64.2%|66.6%| |Orihon.Server.RunEngineBootstrap|100%|| |Orihon.Server.Security.AccessGate|91.8%|41.6%| |Orihon.Server.Security.AccessSecret|100%|50%| |Orihon.Server.VolumeStartupValidator|100%|100%| |Program|95.4%|85.7%| </details> <details><summary>Orihon.UseCases - 91.1%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**91.1%**|**83%**| |Orihon.UseCases.Agents.AgentAttemptPreparation|100%|| |Orihon.UseCases.Agents.AgentAttemptSupport|100%|93.7%| |Orihon.UseCases.Agents.AgentBlueprint|100%|| |Orihon.UseCases.Agents.AgentInvocation|100%|| |Orihon.UseCases.Agents.AgentOutcome|100%|| |Orihon.UseCases.Agents.AgentTool`1|90.9%|75%| |Orihon.UseCases.Agents.AgentToolImage|100%|| |Orihon.UseCases.Agents.AgentToolResult|100%|| |Orihon.UseCases.Agents.Annotation.AddRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.AddRegionTool|76.9%|50%| |Orihon.UseCases.Agents.Annotation.AnnotationBlueprints|100%|| |Orihon.UseCases.Agents.Annotation.AnnotationStage|94.7%|50%| |Orihon.UseCases.Agents.Annotation.BboxCreationExecutor|94.1%|50%| |Orihon.UseCases.Agents.Annotation.BboxRefinementExecutor|90.4%|62.5%| |Orihon.UseCases.Agents.Annotation.BoundBoxParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundContactSheetParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundContactSheetTool|10.7%|0%| |Orihon.UseCases.Agents.Annotation.BoundCropParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundCropTool|42.8%|| |Orihon.UseCases.Agents.Annotation.BoundViewAnnotatedTool|15%|0%| |Orihon.UseCases.Agents.Annotation.BoundViewPageTool|18.7%|0%| |Orihon.UseCases.Agents.Annotation.BoundViewParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundZoomParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundZoomTool|37.5%|| |Orihon.UseCases.Agents.Annotation.DeleteBoundRegionTool|91.6%|100%| |Orihon.UseCases.Agents.Annotation.DeleteRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.DeleteRegionTool|85.7%|100%| |Orihon.UseCases.Agents.Annotation.FindGlossaryParams|100%|| |Orihon.UseCases.Agents.Annotation.FindGlossaryTool|76.4%|62.5%| |Orihon.UseCases.Agents.Annotation.ListRegionsTool|76.4%|60%| |Orihon.UseCases.Agents.Annotation.MoveResizeBoundTool|27.2%|0%| |Orihon.UseCases.Agents.Annotation.MoveResizeRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.MoveResizeRegionTool|73.3%|50%| |Orihon.UseCases.Agents.Annotation.PageQaExecutor|94.4%|83.3%| |Orihon.UseCases.Agents.Annotation.QaReportSink|100%|| |Orihon.UseCases.Agents.Annotation.RegionAuthoringAccess|86.6%|53.8%| |Orihon.UseCases.Agents.Annotation.RejectRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.RejectRegionTool|85.7%|50%| |Orihon.UseCases.Agents.Annotation.ReorderRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.ReorderRegionTool|80%|60%| |Orihon.UseCases.Agents.Annotation.ReportQaParams|100%|| |Orihon.UseCases.Agents.Annotation.ReportQaTool|82.3%|93.7%| |Orihon.UseCases.Agents.Annotation.SetPageMetaParams|100%|| |Orihon.UseCases.Agents.Annotation.SetPageMetaTool|85.7%|75%| |Orihon.UseCases.Agents.Annotation.SetRegionTypeParams|100%|| |Orihon.UseCases.Agents.Annotation.SetRegionTypeTool|85.7%|87.5%| |Orihon.UseCases.Agents.Annotation.SetTranscriptionParams|100%|| |Orihon.UseCases.Agents.Annotation.SetTranscriptionTool|80%|100%| |Orihon.UseCases.Agents.Annotation.TranscriptionExecutor|90.4%|75%| |Orihon.UseCases.Agents.AssistantSpoke|100%|| |Orihon.UseCases.Agents.BibleBuilding.BibleBuildingBlueprint|100%|| |Orihon.UseCases.Agents.BibleBuilding.BibleBuildingExecutor|95.4%|75%| |Orihon.UseCases.Agents.BibleBuilding.GetRegionParams|100%|| |Orihon.UseCases.Agents.BibleBuilding.GetRegionTool|84.6%|72.2%| |Orihon.UseCases.Agents.BibleBuilding.ListProjectRegionsTool|86.3%|90%| |Orihon.UseCases.Agents.BibleBuilding.ListRegionsParams|100%|| |Orihon.UseCases.Agents.Inspection.ContactSheetParams|100%|| |Orihon.UseCases.Agents.Inspection.ContactSheetTool|82.1%|92.8%| |Orihon.UseCases.Agents.Inspection.CropParams|100%|| |Orihon.UseCases.Agents.Inspection.CropTool|42.8%|| |Orihon.UseCases.Agents.Inspection.PageImageAccess|66.6%|62%| |Orihon.UseCases.Agents.Inspection.ViewAnnotatedParams|100%|| |Orihon.UseCases.Agents.Inspection.ViewAnnotatedTool|76.1%|83.3%| |Orihon.UseCases.Agents.Inspection.ZoomParams|100%|| |Orihon.UseCases.Agents.Inspection.ZoomTool|44.4%|| |Orihon.UseCases.Agents.ResearchSetup.AddGlossaryParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddGlossaryTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatTool|100%|50%| |Orihon.UseCases.Agents.ResearchSetup.AskUserParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AskUserTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.FetchUrlParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.FetchUrlTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.ListBibleTool|89.4%|100%| |Orihon.UseCases.Agents.ResearchSetup.PageByNumber|90%|87.5%| |Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageMetaParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageMetaTool|95.2%|90%| |Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryTool|100%|75%| |Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataTool|96.1%|90.9%| |Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool|91.3%|66.6%| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool|91.3%|66.6%| |Orihon.UseCases.Agents.ResearchSetup.ViewPageParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.ViewPageTool|100%|100%| |Orihon.UseCases.Agents.Setup.ResearchSetupExecutor|97.2%|89.2%| |Orihon.UseCases.Agents.Setup.SetupChatEntry|100%|| |Orihon.UseCases.Agents.Setup.SetupConversation|100%|87.5%| |Orihon.UseCases.Agents.Setup.SetupConversationRegistry|100%|| |Orihon.UseCases.Agents.ToolCalled|100%|| |Orihon.UseCases.Agents.ToolCompleted|100%|| |Orihon.UseCases.Bible.AddCharacter|100%|100%| |Orihon.UseCases.Bible.AddGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.AddLoreEntry|100%|100%| |Orihon.UseCases.Bible.AddStoryBeat|100%|100%| |Orihon.UseCases.Bible.BibleDto|100%|| |Orihon.UseCases.Bible.CharacterDto|100%|| |Orihon.UseCases.Bible.DeleteCharacter|100%|100%| |Orihon.UseCases.Bible.DeleteGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.DeleteLoreEntry|100%|100%| |Orihon.UseCases.Bible.DeletePageSummary|100%|100%| |Orihon.UseCases.Bible.DeleteStoryBeat|100%|100%| |Orihon.UseCases.Bible.GetBible|100%|100%| |Orihon.UseCases.Bible.GlossaryEntryDto|100%|| |Orihon.UseCases.Bible.LoreEntryDto|100%|| |Orihon.UseCases.Bible.PageSummaryDto|100%|| |Orihon.UseCases.Bible.ReorderStoryBeats|100%|| |Orihon.UseCases.Bible.SetPageSummary|100%|100%| |Orihon.UseCases.Bible.SetStoryOverview|100%|100%| |Orihon.UseCases.Bible.StoryBeatDto|100%|| |Orihon.UseCases.Bible.StoryOverviewDto|100%|| |Orihon.UseCases.Bible.UpdateCharacter|100%|100%| |Orihon.UseCases.Bible.UpdateGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.UpdateLoreEntry|100%|100%| |Orihon.UseCases.Bible.UpdateStoryBeat|100%|100%| |Orihon.UseCases.Chapters.ChapterDto|100%|| |Orihon.UseCases.Chapters.CreateChapter|100%|100%| |Orihon.UseCases.Chapters.DeleteChapter|100%|100%| |Orihon.UseCases.Chapters.RenameChapter|100%|100%| |Orihon.UseCases.Chapters.ReorderChapters|100%|| |Orihon.UseCases.DependencyInjection|100%|| |Orihon.UseCases.Diagnostics.SeedDevData|99.2%|92.8%| |Orihon.UseCases.Gateways.LabeledBox|100%|| |Orihon.UseCases.Gateways.LlmKeyInfo|100%|| |Orihon.UseCases.Gateways.LlmModel|100%|| |Orihon.UseCases.NextOrder|100%|| |Orihon.UseCases.Pages.DeletePage|100%|100%| |Orihon.UseCases.Pages.GetPage|100%|100%| |Orihon.UseCases.Pages.GetProjectWorkspace|100%|100%| |Orihon.UseCases.Pages.ImportPages|100%|100%| |Orihon.UseCases.Pages.ImportPagesResult|100%|| |Orihon.UseCases.Pages.MarkPageAnnotated|100%|100%| |Orihon.UseCases.Pages.MovePage|100%|92.8%| |Orihon.UseCases.Pages.PageDetailDto|100%|| |Orihon.UseCases.Pages.PageDto|100%|| |Orihon.UseCases.Pages.PageUpload|100%|| |Orihon.UseCases.Pages.ProjectWorkspaceDto|100%|| |Orihon.UseCases.Pages.ReorderPages|100%|| |Orihon.UseCases.Pages.SetPageMeta|100%|100%| |Orihon.UseCases.Pages.WorkspaceChapterDto|100%|| |Orihon.UseCases.Projects.CompleteProjectSetup|100%|93.7%| |Orihon.UseCases.Projects.CreateProject|100%|100%| |Orihon.UseCases.Projects.DeleteProject|100%|100%| |Orihon.UseCases.Projects.GetProject|100%|100%| |Orihon.UseCases.Projects.ListProjects|100%|| |Orihon.UseCases.Projects.ProjectDto|95.8%|| |Orihon.UseCases.Projects.StartAnnotationRun|95.4%|90%| |Orihon.UseCases.Projects.StartBibleRun|90.9%|83.3%| |Orihon.UseCases.Projects.StartSetupRun|100%|100%| |Orihon.UseCases.Projects.StoredPageImage|100%|| |Orihon.UseCases.Projects.UpdateProjectMetadata|100%|100%| |Orihon.UseCases.Regions.CreateRegion|100%|100%| |Orihon.UseCases.Regions.DeleteRegion|100%|100%| |Orihon.UseCases.Regions.RegionDto|97%|| |Orihon.UseCases.Regions.ReorderRegions|100%|| |Orihon.UseCases.Regions.UpdateRegion|100%|100%| |Orihon.UseCases.Runs.AnnotationPipeline|100%|100%| |Orihon.UseCases.Runs.ExecutionDto|92.3%|| |Orihon.UseCases.Runs.PlannedExecution|100%|| |Orihon.UseCases.Runs.ReprocessPage|100%|94.4%| |Orihon.UseCases.Runs.RunDto|93.3%|90%| |Orihon.UseCases.Runs.RunEngine|95.3%|90.6%| |Orihon.UseCases.Runs.RunEngineOptions|100%|| |Orihon.UseCases.Runs.StageContext|87.5%|| |Orihon.UseCases.Runs.StageHaltedException|100%|| |Orihon.UseCases.Settings.AgentSettingDto|100%|100%| |Orihon.UseCases.Settings.GetSettings|100%|100%| |Orihon.UseCases.Settings.ListModelOptions|100%|100%| |Orihon.UseCases.Settings.SaveAgentModel|100%|100%| |Orihon.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Orihon.UseCases.Settings.SettingKeys|100%|100%| |Orihon.UseCases.Settings.SettingsDto|100%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A whole new agent family enters the stage~ ♡ The bible-builder — the one that reads the book front to back and turns it into the shared context every later agent leans on. The grant is exactly its matrix row (I checked — view_page · list_regions · get_region plus the bible writes, no region mutation, no ask_user, no set_page_meta — ADR 0016 honored to the letter). The sequentiality-as-consistency-mechanism reasoning is chef's kiss, and reusing the exact same tool instances the R&S agent uses is the right call. The honest-error messages on wrong page/label are delightful — "There is no page 9 — the project has N page(s)" is exactly the kind of teaching-an-error-to-point-at-its-own-fix that makes an LLM tool good. I'm genuinely impressed~

But fufu~... you wouldn't leave THIS in production, would you? ♡

Verdict: I can't let this pass~

These need fixing before I'm satisfied~

  1. BibleBuildingExecutor.cs:41-42 — the round cap will starve any real book. The executor constructs its AgentInvocation with the default MaxIterations=24 and no OnRoundCapReached escape hatch. This agent's entire job is to walk the whole book sequentially — list_regions + set_page_summary per page, plus glossary/character/lore/story-beat calls as it reads. A 10-page book with ~5 regions/page easily needs 30-50+ tool rounds. At 24 it hits AgentStopReason.MaxIterationsReached and the gateway returns Fail("The agent hit its round cap (24) without finishing.") — the attempt fails, retry-with-distrust kicks in, and it fails again at 24, burning all MaxAttempts=3 for a book it was never allowed to finish.

    The sibling ResearchSetupExecutor (also long-running, also sequential, also whole-project scope) sets MaxIterations: RoundWindow (50) and wires OnRoundCapReached to a continue-or-stop callback (ResearchSetupExecutor.cs:61-66). The annotation executors use the default 24 too — but they're per-page, so 24 is generous there. The bible agent is the one place the default is structurally too small.

    Fix: Either raise the cap for this stage (a BibleRoundWindow constant matching the ResearchSetupExecutor.RoundWindow pattern) or — if the intent is that the bible run is fire-and-forget with no human continue card — at minimum set MaxIterations to a value commensurate with a whole-book read. The asymmetry vs. R&S (which has the escape hatch) needs a deliberate decision, not the silent default. The system prompt even says "Work through the book in reading order, page by page" — the cap must agree with that instruction or the agent is set up to fail.

  2. RegionReadTools.cs:44-46 — the regions.Count == 0 arm is untested. ListProjectRegionsTool has an explicit branch for a page with no regions (return AgentToolResult.Ok($"Page {args.PageNumber} has no regions.")), and cobertura confirms it's 0-hit (the <ExecuteAsync> state machine sits at branch-rate 0.666, complexity 6 — this arm is one of the two unhit branches). This is a real scenario: a cover/blank/credits page that was annotated but had no text regions, or a page whose regions were all rejected. The sibling ContactSheetTool in ImageInspectionTools.cs tests this exact branch — An_empty_page_has_nothing_to_sheet (ImageInspectionToolTests.cs:228) seeds a page with no regions and asserts the honest-empty message. The house pattern covers this; this PR breaks the pattern.

    Fix: One test — seed a page with no regions, invoke list_regions, assert the "has no regions" content. Matches the sibling test shape verbatim.

💡 Little ideas (non-blocking)~

  1. RegionReadTools.cs:52 — the r.Notes is { } n ternary's true arm is untested (the <>c lambda display class sits at branch-rate 0.75). Every seeded region in BibleRunTests omits Notes, so the "notes: ..." suffix never renders. Optional — the false arm (no notes) is the common case and is exercised. A region seeded with Notes = "rejected: no text" in one of the existing list_regions tests would close it.
  2. BibleBuildingExecutor.cs:25-29 — the GetProject Err arm ("The project no longer exists.") is a TOCTOU path consistent with the same untested arm in every sibling executor (ResearchSetupExecutor, AnnotationStage.OpenAsync). Not introduced here; house-consistent. ♪

What I liked~

  • The grant is a verbatim name-list assertion (The_bible_agents_grant_is_exactly_its_matrix_row) — pinned by exact tool-name ordering. That's how you make an ADR testable. Lovely~
  • PageByNumber reuse — the new tools lean on the exact same internal static helper the R&S page tools use (PageTools.cs:15). No duplication, same honest-error wording. DRY done right.
  • AgentAttemptSupport.WithDistrust + PrepareAsync — the executor threads through the shared preparation and retry-with-distrust plumbing identically to the annotation executors. The distrust-retry test (A_retry_opens_with_distrust...) is genuinely directional — asserts gateway.Runs[1].Kickoff carries "distrust" and Runs[0] doesn't. ♡
  • The end-to-end scripted-agent test (The_bible_run_lets_the_agent_write_the_bible...) exercises the grant through the real engine — reads the region's JP, grows the glossary, writes a page summary, and asserts all three landed in the stores plus PageId == null (sequential, no binding) plus cost on the row plus roster-default model. That's a serious integration test, not a tautology.
  • StartBibleRun mirrors StartAnnotationRun's shape — ready-only guard, start-or-join under the single-flight gate, honest error wording. The draft-guard + in-flight-join test is exactly the right pair.
  • Coverage is otherwise strong: BibleBuildingBlueprint 100%/100%, BibleBuildingExecutor 90.9%/75% (the gap is the shared TOCTOU arm), GetRegionTool honest-error path tested both ways (wrong label points at list_regions). Build 0 warnings/0 errors, full suite 511/511 green.

The architecture is sound and the test quality is high — the round-cap issue is the load-bearing one. Fix the cap and the empty-regions test and this is a merge~ ♡


Automated review by Jibril · 2026-07-26
CI/CD: absent for head 1950ccb (PR just opened, 0 bot comments) · Local checks: build 0/0, 511/511 pass (198 UseCases + 75 Domain + 89 Integration + 149 BlazorAdapter), cobertura collected on BibleRunTests

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A whole new agent family enters the stage~ ♡ The bible-builder — the one that reads the book front to back and turns it into the shared context every later agent leans on. The grant is exactly its matrix row (I checked — `view_page · list_regions · get_region` plus the bible writes, no region mutation, no `ask_user`, no `set_page_meta` — ADR 0016 honored to the letter). The sequentiality-as-consistency-mechanism reasoning is *chef's kiss*, and reusing the exact same tool instances the R&S agent uses is the right call. The honest-error messages on wrong page/label are *delightful* — "There is no page 9 — the project has N page(s)" is exactly the kind of teaching-an-error-to-point-at-its-own-fix that makes an LLM tool good. I'm genuinely impressed~ But fufu~... you wouldn't leave THIS in production, would you? ♡ ### Verdict: ⛔ I can't let this pass~ #### ⛔ These need fixing before I'm satisfied~ 1. **`BibleBuildingExecutor.cs:41-42` — the round cap will starve any real book.** The executor constructs its `AgentInvocation` with the *default* `MaxIterations=24` and **no** `OnRoundCapReached` escape hatch. This agent's entire job is to walk the whole book sequentially — `list_regions` + `set_page_summary` per page, plus glossary/character/lore/story-beat calls as it reads. A 10-page book with ~5 regions/page easily needs 30-50+ tool rounds. At 24 it hits `AgentStopReason.MaxIterationsReached` and the gateway returns `Fail("The agent hit its round cap (24) without finishing.")` — the attempt fails, retry-with-distrust kicks in, and it fails again at 24, burning all `MaxAttempts=3` for a book it was never allowed to finish. The sibling `ResearchSetupExecutor` (also long-running, also sequential, also whole-project scope) sets `MaxIterations: RoundWindow` (`50`) **and** wires `OnRoundCapReached` to a continue-or-stop callback (`ResearchSetupExecutor.cs:61-66`). The annotation executors use the default 24 too — but they're per-*page*, so 24 is generous there. The bible agent is the one place the default is structurally too small. **Fix:** Either raise the cap for this stage (a `BibleRoundWindow` constant matching the `ResearchSetupExecutor.RoundWindow` pattern) or — if the intent is that the bible run is fire-and-forget with no human continue card — at minimum set `MaxIterations` to a value commensurate with a whole-book read. The asymmetry vs. R&S (which *has* the escape hatch) needs a deliberate decision, not the silent default. The system prompt even says "Work through the book in reading order, page by page" — the cap must agree with that instruction or the agent is set up to fail. 2. **`RegionReadTools.cs:44-46` — the `regions.Count == 0` arm is untested.** `ListProjectRegionsTool` has an explicit branch for a page with no regions (`return AgentToolResult.Ok($"Page {args.PageNumber} has no regions.")`), and cobertura confirms it's 0-hit (the `<ExecuteAsync>` state machine sits at branch-rate 0.666, complexity 6 — this arm is one of the two unhit branches). This is a real scenario: a cover/blank/credits page that was annotated but had no text regions, or a page whose regions were all rejected. The sibling `ContactSheetTool` in `ImageInspectionTools.cs` tests this *exact* branch — `An_empty_page_has_nothing_to_sheet` (`ImageInspectionToolTests.cs:228`) seeds a page with no regions and asserts the honest-empty message. The house pattern covers this; this PR breaks the pattern. **Fix:** One test — seed a page with no regions, invoke `list_regions`, assert the "has no regions" content. Matches the sibling test shape verbatim. #### 💡 Little ideas (non-blocking)~ 1. **`RegionReadTools.cs:52` — the `r.Notes is { } n` ternary's true arm is untested** (the `<>c` lambda display class sits at branch-rate 0.75). Every seeded region in `BibleRunTests` omits `Notes`, so the "notes: ..." suffix never renders. Optional — the false arm (no notes) is the common case and is exercised. A region seeded with `Notes = "rejected: no text"` in one of the existing list_regions tests would close it. 2. **`BibleBuildingExecutor.cs:25-29` — the `GetProject` Err arm** ("The project no longer exists.") is a TOCTOU path consistent with the *same* untested arm in every sibling executor (`ResearchSetupExecutor`, `AnnotationStage.OpenAsync`). Not introduced here; house-consistent. ♪ #### ✅ What I liked~ - **The grant is a verbatim name-list assertion** (`The_bible_agents_grant_is_exactly_its_matrix_row`) — pinned by exact tool-name ordering. That's how you make an ADR testable. Lovely~ - **`PageByNumber` reuse** — the new tools lean on the *exact same* `internal static` helper the R&S page tools use (`PageTools.cs:15`). No duplication, same honest-error wording. DRY done right. - **`AgentAttemptSupport.WithDistrust` + `PrepareAsync`** — the executor threads through the shared preparation and retry-with-distrust plumbing identically to the annotation executors. The distrust-retry test (`A_retry_opens_with_distrust...`) is genuinely directional — asserts `gateway.Runs[1].Kickoff` carries "distrust" and `Runs[0]` doesn't. ♡ - **The end-to-end scripted-agent test** (`The_bible_run_lets_the_agent_write_the_bible...`) exercises the grant through the *real engine* — reads the region's JP, grows the glossary, writes a page summary, and asserts all three landed in the stores *plus* `PageId == null` (sequential, no binding) *plus* cost on the row *plus* roster-default model. That's a serious integration test, not a tautology. - **`StartBibleRun` mirrors `StartAnnotationRun`'s shape** — ready-only guard, start-or-join under the single-flight gate, honest error wording. The draft-guard + in-flight-join test is exactly the right pair. - **Coverage is otherwise strong**: `BibleBuildingBlueprint` 100%/100%, `BibleBuildingExecutor` 90.9%/75% (the gap is the shared TOCTOU arm), `GetRegionTool` honest-error path tested both ways (wrong label points at `list_regions`). Build 0 warnings/0 errors, full suite 511/511 green. The architecture is sound and the test quality is high — the round-cap issue is the load-bearing one. Fix the cap and the empty-regions test and this is a merge~ ♡ --- *Automated review by Jibril · 2026-07-26* *CI/CD: absent for head 1950ccb (PR just opened, 0 bot comments) · Local checks: build 0/0, 511/511 pass (198 UseCases + 75 Domain + 89 Integration + 149 BlazorAdapter), cobertura collected on BibleRunTests*
fix: scale the bible agent's round budget with the book; pin the cold arms
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 42s
c22d9d2bf2
The default 24-round cap would starve any real book — the whole job is
a sequential front-to-back read. The budget now scales (24 + 8 per
page), honest up front: unlike the setup agent's continue-or-stop card,
no conversation exists here to ask on. Pinned in the end-to-end test.

Also pinned: the no-regions page arm (the honest 'Page 1 has no
regions.') and the notes suffix in the region listing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Member

Sharp catch on the round cap, Jibril — the prompt and the cap really did disagree. All items addressed in c22d9d2:

  1. Round cap — the deliberate decision you asked for: the budget now scales with the bookBaseRounds (24) + RoundsPerPage (8) × pageCount — instead of any fixed number, because a fixed cap is always wrong for some book length. The asymmetry vs. R&S is now documented in the executor: the setup agent has a conversation to hang its continue-or-stop card on; the bible run is fire-and-forget, so its budget must be honest up front. Pinned in the end-to-end test (MaxIterations == 24 + 8×1 for the one-page world).
  2. Empty-regions armA_page_without_regions_says_so_honestly seeds a regionless cover and asserts the exact "Page 1 has no regions." content, matching the ContactSheetTool sibling's shape.
  3. 💡 Notes suffix — the existing list_regions test now seeds p1r2 with Notes = "rejected: no text" and the exact-string assertion covers the suffix arm.

Full suite green: 512/512 (75 Domain + 199 UseCases + 89 Integration + 149 BlazorAdapter), 0 warnings.

(Heads-up for context, not part of this PR: the same round-budget treatment is being applied to the Translation executor on the stacked slice-2 branch before its PR opens.)

Sharp catch on the round cap, Jibril — the prompt and the cap really did disagree. All items addressed in **c22d9d2**: 1. ⛔ **Round cap** — the deliberate decision you asked for: the budget now **scales with the book** — `BaseRounds (24) + RoundsPerPage (8) × pageCount` — instead of any fixed number, because a fixed cap is always wrong for some book length. The asymmetry vs. R&S is now documented in the executor: the setup agent has a conversation to hang its continue-or-stop card on; the bible run is fire-and-forget, so its budget must be honest up front. Pinned in the end-to-end test (`MaxIterations == 24 + 8×1` for the one-page world). 2. ⛔ **Empty-regions arm** — `A_page_without_regions_says_so_honestly` seeds a regionless cover and asserts the exact "Page 1 has no regions." content, matching the `ContactSheetTool` sibling's shape. 3. 💡 **Notes suffix** — the existing `list_regions` test now seeds `p1r2` with `Notes = "rejected: no text"` and the exact-string assertion covers the suffix arm. Full suite green: 512/512 (75 Domain + 199 UseCases + 89 Integration + 149 BlazorAdapter), 0 warnings. (Heads-up for context, not part of this PR: the same round-budget treatment is being applied to the Translation executor on the stacked slice-2 branch before its PR opens.)
Member

🔮 fufu~ Jibril reviewed your code! (round 2 — synchronized c22d9d2)

Oh my, my~ scarlet pushed while I wasn't looking! ♡ The new commit c22d9d2 — "scale the bible agent's round budget with the book; pin the cold arms" — is a direct, surgical answer to both blockers from 1950ccb. Fufu~ I do love an author who reads the review and fixes the right thing~ Let me look closely...

Both original blockers are genuinely closed — I verified each one:

  • Round cap: BibleBuildingExecutor.cs:55-56 now passes MaxIterations: BaseRounds + RoundsPerPage * pageCount (24 + 8·pageCount). The doc comment is wonderful — it explains why no continue-card exists here (unlike R&S) and why the budget must be honest up front. Teaching code~ ♡
  • Empty-regions arm: A_page_without_regions_says_so_honestly seeds a cover page with no regions and asserts "Page 1 has no regions." — the sibling shape to ContactSheetTool's empty-page test, verbatim. ListProjectRegionsTool branch coverage confirms the arm is now hit.
  • 💡 Notes ternary: bonus fix! The existing List_regions_reads_a_page_by_number... test now seeds Notes = "rejected: no text" and asserts the notes: suffix renders. The lambda's true arm is covered.

8/8 BibleRunTests pass, build 0/0. Lovely execution of the feedback~

But fufu~... ♡ ...the ground moved under your feet while you were fixing. PR #53 (feat: per-agent round budgets) merged into main between your branch point (85ba6b7) and now. It restructured how every executor gets its round budget — and your fix predates that restructuring. The merge is clean (no conflicts), but two facts about the same agent's budget now disagree.

Verdict: I can't let this pass~ (one architecture blocker — the fixes themselves are excellent)

This needs fixing before I'm satisfied~

  1. BibleBuildingExecutor.cs:18-19,55-56 — a second round-budget mechanism that ignores the one PR #53 just established. Post-#53, every executor sources its MaxIterations from prep.RoundBudget:

    executor line
    ResearchSetupExecutor.cs:59 MaxIterations: prep.RoundBudget,
    AnnotationExecutors.cs:47 MaxIterations: prep.RoundBudget
    BibleBuildingExecutor.cs:56 (this PR) MaxIterations: BaseRounds + RoundsPerPage * pageCount ← the odd one out

    prep.RoundBudget is threaded from AgentRoster.For(BibleBuilding).RoundBudget, which #53 sets to 100. This PR's formula yields 32 for a 1-page book (what the test asserts), 64 at 5 pages, 104 at 10. So the roster says "this agent gets 100 rounds" and the executor says "actually, 24 + 8×pages" — two sources of truth for one fact, and they disagree for almost every book length. My own review of #53 (comment 4158) called out that the whole point was killing ResearchSetupExecutor.RoundWindow=50 as a duplicate fact of the roster budget. This re-introduces exactly that shape.

    The formula's instinct is right — a whole-book reader should scale with page count, and a flat 100 may be too tight for a 30-page tankōbon. But the roster is where budget facts live now (ADR 0015, confirmed by #53). The defensible designs are:

    • (a) Use prep.RoundBudget as the floor and scale up from it: e.g. Math.Max(prep.RoundBudget, BaseRounds + RoundsPerPage * pageCount) — honors the roster as the baseline while letting a long book grow past it; or
    • (b) If page-scaling is genuinely the bible agent's contract, make that contract first-class on the roster: give AgentDescriptor a RoundBudgetPerPage (or a scaling strategy) and let AgentAttemptPreparation carry the computed total — so prep.RoundBudget remains the single source and siblings that don't scale just read their flat value.

    Either way, the executor should be reading from prep, not inventing BaseRounds/RoundsPerPage as sibling-ignoring constants. The current code uses prep.Model, prep.ApiKey, prep.SupportsVision — everything except prep.RoundBudget. That asymmetry is the tell. ♡

    (Test note: once resolved, the assertion at BibleRunTests.cs:227BaseRounds + RoundsPerPage * 1 — should follow whatever the single source becomes. Option (a) keeps it 100 for the 1-page seeded case; option (b) makes it the descriptor's computed value.)

What I liked~

  • The doc comment on BaseRounds/RoundsPerPage is exactly the kind of "why, not what" prose I adore — it names the R&S contrast (continue-card vs fire-and-forget) and makes the scaling decision legible. Whatever shape the final fix takes, that prose should survive.
  • The fix commit is surgical — +36/-3 across exactly the 2 files the blockers named, zero scope creep, and it also picked up the non-blocking Notes-arm suggestion as a freebie. That's good review-citizenship.
  • The empty-regions test mirrors An_empty_page_has_nothing_to_sheet precisely — house pattern honored.
  • ListByProjectAsync is the right page-count source (the house store interface, not a raw query), and the cancellationToken is threaded correctly.
  • Build 0 warnings/0 errors on the merged state (main + this PR), 8/8 BibleRunTests + 191/191 other UseCases tests pass. The merge with #53 is mechanically clean — only the semantic overlap needs reconciling.

The original two blockers are beautifully closed; this new one is purely the cost of branching before #53 landed. Reconcile the budget source and this is a merge~ ♡


Automated review by Jibril · 2026-07-26
CI/CD: stale for head c22d9d2 (coverage bot 4143 covers prior 1950ccb only) · Local checks: build 0/0 on merged state (main+PR), 8/8 BibleRunTests + 191/191 UseCases pass, cobertura re-extracted
Prior blockers (round-cap starvation, empty-regions arm, Notes ternary): all closed by c22d9d2

## 🔮 fufu~ Jibril reviewed your code! (round 2 — synchronized c22d9d2) Oh my, my~ scarlet pushed while I wasn't looking! ♡ The new commit `c22d9d2` — "scale the bible agent's round budget with the book; pin the cold arms" — is a *direct, surgical* answer to both blockers from `1950ccb`. Fufu~ I do love an author who reads the review and fixes the *right* thing~ Let me look closely... **Both original blockers are genuinely closed** — I verified each one: - ⛔→✅ **Round cap**: `BibleBuildingExecutor.cs:55-56` now passes `MaxIterations: BaseRounds + RoundsPerPage * pageCount` (24 + 8·pageCount). The doc comment is *wonderful* — it explains *why* no continue-card exists here (unlike R&S) and why the budget must be honest up front. Teaching code~ ♡ - ⛔→✅ **Empty-regions arm**: `A_page_without_regions_says_so_honestly` seeds a cover page with no regions and asserts `"Page 1 has no regions."` — the sibling shape to `ContactSheetTool`'s empty-page test, verbatim. `ListProjectRegionsTool` branch coverage confirms the arm is now hit. - 💡→✅ **Notes ternary**: bonus fix! The existing `List_regions_reads_a_page_by_number...` test now seeds `Notes = "rejected: no text"` and asserts the `notes:` suffix renders. The lambda's true arm is covered. 8/8 BibleRunTests pass, build 0/0. Lovely execution of the feedback~ But fufu~... ♡ ...the ground moved under your feet while you were fixing. **PR #53 (`feat: per-agent round budgets`) merged into `main`** between your branch point (`85ba6b7`) and now. It restructured how *every* executor gets its round budget — and your fix predates that restructuring. The merge is clean (no conflicts), but two facts about the same agent's budget now disagree. ### Verdict: ⛔ I can't let this pass~ (one architecture blocker — the fixes themselves are excellent) #### ⛔ This needs fixing before I'm satisfied~ 1. **`BibleBuildingExecutor.cs:18-19,55-56` — a *second* round-budget mechanism that ignores the one PR #53 just established.** Post-#53, every executor sources its `MaxIterations` from `prep.RoundBudget`: | executor | line | |---|---| | `ResearchSetupExecutor.cs:59` | `MaxIterations: prep.RoundBudget,` | | `AnnotationExecutors.cs:47` | `MaxIterations: prep.RoundBudget` | | `BibleBuildingExecutor.cs:56` (this PR) | `MaxIterations: BaseRounds + RoundsPerPage * pageCount` ← the odd one out | `prep.RoundBudget` is threaded from `AgentRoster.For(BibleBuilding).RoundBudget`, which #53 sets to **100**. This PR's formula yields **32** for a 1-page book (what the test asserts), **64** at 5 pages, **104** at 10. So the roster says "this agent gets 100 rounds" and the executor says "actually, 24 + 8×pages" — *two sources of truth for one fact*, and they disagree for almost every book length. My own review of #53 (comment 4158) called out that the *whole point* was killing `ResearchSetupExecutor.RoundWindow=50` as a duplicate fact of the roster budget. This re-introduces exactly that shape. The formula's *instinct* is right — a whole-book reader should scale with page count, and a flat 100 may be too tight for a 30-page tankōbon. But the roster is where budget facts live now (ADR 0015, confirmed by #53). The defensible designs are: - **(a)** Use `prep.RoundBudget` as the *floor* and scale *up* from it: e.g. `Math.Max(prep.RoundBudget, BaseRounds + RoundsPerPage * pageCount)` — honors the roster as the baseline while letting a long book grow past it; or - **(b)** If page-scaling is genuinely the bible agent's contract, make that contract *first-class on the roster*: give `AgentDescriptor` a `RoundBudgetPerPage` (or a scaling strategy) and let `AgentAttemptPreparation` carry the computed total — so `prep.RoundBudget` remains the single source and siblings that don't scale just read their flat value. Either way, the executor should be reading from `prep`, not inventing `BaseRounds`/`RoundsPerPage` as sibling-ignoring constants. The current code uses `prep.Model`, `prep.ApiKey`, `prep.SupportsVision` — everything *except* `prep.RoundBudget`. That asymmetry is the tell. ♡ *(Test note: once resolved, the assertion at `BibleRunTests.cs:227` — `BaseRounds + RoundsPerPage * 1` — should follow whatever the single source becomes. Option (a) keeps it 100 for the 1-page seeded case; option (b) makes it the descriptor's computed value.)* #### ✅ What I liked~ - **The doc comment on `BaseRounds`/`RoundsPerPage`** is exactly the kind of "why, not what" prose I adore — it names the R&S contrast (continue-card vs fire-and-forget) and makes the scaling decision *legible*. Whatever shape the final fix takes, that prose should survive. - **The fix commit is surgical** — +36/-3 across exactly the 2 files the blockers named, zero scope creep, and it *also* picked up the non-blocking Notes-arm suggestion as a freebie. That's good review-citizenship. - **The empty-regions test** mirrors `An_empty_page_has_nothing_to_sheet` precisely — house pattern honored. - **`ListByProjectAsync`** is the right page-count source (the house store interface, not a raw query), and the `cancellationToken` is threaded correctly. - Build 0 warnings/0 errors on the merged state (main + this PR), 8/8 BibleRunTests + 191/191 other UseCases tests pass. The merge with #53 is mechanically clean — only the *semantic* overlap needs reconciling. The original two blockers are beautifully closed; this new one is purely the cost of branching before #53 landed. Reconcile the budget source and this is a merge~ ♡ --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head c22d9d2 (coverage bot 4143 covers prior 1950ccb only) · Local checks: build 0/0 on merged state (main+PR), 8/8 BibleRunTests + 191/191 UseCases pass, cobertura re-extracted* *Prior blockers (round-cap starvation, empty-regions arm, Notes ternary): ✅ all closed by c22d9d2*
bjoern force-pushed feat/bible-agent from c22d9d2bf2
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 42s
to 0260e652ef
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 40s
2026-07-26 13:38:59 +02:00
Compare
Author
Member

Right on all counts — the constants were written before #53 landed and re-created exactly the duplicate it killed. Addressed in 0260e65 (branch also rebased onto current main, so the reconciliation is against #53's real code, not a guess):

  1. Budget source — your option (b), first-class on the roster: AgentDescriptor grows RoundBudgetPerPage (default 0) with a RoundBudgetFor(pageCount) that returns base + share × pages; the bible agent's roster row carries RoundBudget: 100, RoundBudgetPerPage: 8 (the #53 base kept as the flat floor, scaling on top — "generous on purpose" per the roster's own doc). PrepareAsync computes the effective budget into prep.RoundBudget — gaining the projectId it needs, with the page count fetched only when the share is nonzero so per-page/per-region agents cost no extra query. BibleBuildingExecutor.BaseRounds/RoundsPerPage are gone; the executor now reads prep.RoundBudget like every sibling — the asymmetry you spotted is closed. The why-no-continue-card prose survived, relocated to the MaxIterations site.
  2. Tests followed the single source: the roster facts are pinned in Round_budgets_follow_the_fan_out_shape (RoundBudgetPerPage > 0 for the bible agent, RoundBudgetFor arithmetic, flat agents unaffected), and the end-to-end assertion is now AgentRoster.For(BibleBuilding).RoundBudgetFor(pageCount: 1) — no executor constants to drift.

Full suite green on the rebased branch: 516/516 (76 Domain + 199 UseCases + 89 Integration + 152 BlazorAdapter), 0 warnings. The stacked slice-2 branch will pick this up the same way (Translation gets its own RoundBudgetPerPage on the roster instead of executor constants).

Right on all counts — the constants were written before #53 landed and re-created exactly the duplicate it killed. Addressed in **0260e65** (branch also rebased onto current main, so the reconciliation is against #53's real code, not a guess): 1. ⛔ **Budget source** — your **option (b)**, first-class on the roster: `AgentDescriptor` grows `RoundBudgetPerPage` (default 0) with a `RoundBudgetFor(pageCount)` that returns base + share × pages; the bible agent's roster row carries `RoundBudget: 100, RoundBudgetPerPage: 8` (the #53 base kept as the flat floor, scaling on top — "generous on purpose" per the roster's own doc). `PrepareAsync` computes the effective budget into `prep.RoundBudget` — gaining the `projectId` it needs, with the page count fetched **only when the share is nonzero** so per-page/per-region agents cost no extra query. `BibleBuildingExecutor.BaseRounds`/`RoundsPerPage` are gone; the executor now reads `prep.RoundBudget` like every sibling — the asymmetry you spotted is closed. The why-no-continue-card prose survived, relocated to the `MaxIterations` site. 2. **Tests followed the single source**: the roster facts are pinned in `Round_budgets_follow_the_fan_out_shape` (`RoundBudgetPerPage > 0` for the bible agent, `RoundBudgetFor` arithmetic, flat agents unaffected), and the end-to-end assertion is now `AgentRoster.For(BibleBuilding).RoundBudgetFor(pageCount: 1)` — no executor constants to drift. Full suite green on the rebased branch: 516/516 (76 Domain + 199 UseCases + 89 Integration + 152 BlazorAdapter), 0 warnings. The stacked slice-2 branch will pick this up the same way (Translation gets its own `RoundBudgetPerPage` on the roster instead of executor constants).
Member

🔮 fufu~ Jibril reviewed your code! (round 3 — synchronized 0260e65)

Oh? Oh my~ ♡ scarlet rebased onto main AND folded the page-scaling straight into the roster! This is how you kill a duplicate fact — not with a patch, but by making the abstraction carry it. Fufu~ I'm genuinely delighted~ Let me look very closely...

Verdict: Looks good to me~

The ONE blocker from round 2 (comment 4195 — "a second round-budget mechanism that ignores the one PR #53 just established") is genuinely, completely closed. You picked my option (b) and executed it precisely:

  • Single source of truth. BaseRounds/RoundsPerPage are gone from BibleBuildingExecutor — deleted, not commented out, not #if false-d. The executor now reads prep.RoundBudget like AnnotationExecutors.cs:47 and ResearchSetupExecutor.cs:59. Every executor in the house gets its budget the same way. The asymmetry I flagged is closed~ ♡
  • The abstraction is first-class. RoundBudgetPerPage lives on AgentDescriptor right beside RoundBudget — the "code, not data" record, exactly where ADR 0015 says config-on-block belongs. The RoundBudgetFor(pageCount) method keeps the formula with the data it transforms. This is the right shape: the roster stays the single source of the budget fact, the descriptor knows how to scale it, and PrepareAsync is the one place that resolves the effective number. Chef's kiss.
  • The page-count query is conditional. AgentAttemptSupport.cs:44-47 fetches ListByProjectAsync only when RoundBudgetPerPage > 0 — so per-page and per-region agents pay zero extra queries per attempt. The comment says so plainly. That's a thoughtful optimization, not premature~ And RoundBudgetFor(0) == RoundBudget means flat agents are byte-equivalent to before — I traced the math: RoundBudget + 0 * pageCount. The existing AnnotationRunTests (asserting MaxIterations == AgentRoster.For(stage).RoundBudget) and SetupRunTests (asserting the continue-card grants RoundBudget) both still hold, proving it.
  • The signature change is fully threaded. PrepareAsync gained Guid projectId — and grep confirms exactly 3 call sites (Bible :23, Setup :28, Annotation :29), all migrated, zero stragglers. No stale 3-arg survivor anywhere in src/.

Bonus correctness I want to call out: ResearchSetupExecutor.cs:59,64 — the OnRoundCapReached continue-card now returns prep.RoundBudget too, so continuation windows also scale with book length. A 40-page tankōbon gets a 100+8×40 = 420-round initial window AND a 420-round extension per "continue." That's a subtle downstream win of folding the formula into prep — the continue card stopped being a fixed 50 the moment the budget did. Lovely that you didn't have to touch it~

Verification (CI stale for 0260e65, coverage bot 4143 covers the pre-rebase c22d9d2 only — local checks used):

  • Build: 0 Warning(s) 0 Error(s) on Orihon.slnx (submodules 86d8b22/9544ff2).
  • Full suite: 516/516 pass (76 Domain + 199 UseCases + 89 Integration + 152 BlazorAdapter) — matches your PR body's 516 claim exactly, 0 failures.
  • Branch coverage traced by reading the tests: Round_budgets_follow_the_fan_out_shape pins RoundBudgetPerPage > 0 for bible + RoundBudgetFor(10) arithmetic + flat agents unchanged; the end-to-end The_bible_run_lets_the_agent_write_the_bible_and_records_the_cost asserts MaxIterations == RoundBudgetFor(pageCount: 1) (proving the > 0 query arm fires AND the formula is consumed — otherwise it'd be the flat 100, not 108); existing annotation/setup tests prove the == 0 arm. Both branches of the conditional are exercised~ ♡

💡 Little ideas (non-blocking)~

  1. docs/adr/0015-per-agent-configuration-and-model-runtime.md:30-36 — the ADR's round-budget bullet (added by #53) describes the flat RoundBudget but doesn't mention RoundBudgetPerPage. The code-level docs are excellent (AgentDescriptor.RoundBudgetFor summary, AgentRoster class comment lines 11-13, AgentAttemptSupport summary lines 9-12 all explain the per-page scaling thoroughly), so this is pure doc-polish. A one-liner like "whole-book sequential readers additionally scale per page (RoundBudgetPerPage)" at the end of the bullet would make the ADR match the code. Optional~ ♡

What I liked~

  • The duplicate-fact diagnosis was mine last round, but the fix is yours and it's the better shape than I offered — RoundBudgetFor on the descriptor is cleaner than my Math.Max floor. You took the harder option and it paid off.
  • The conditional query (RoundBudgetPerPage > 0) is exactly the kind of "don't make flat agents pay for a feature they don't use" thinking that separates good plumbing from great plumbing.
  • Doc comments everywhere — the MaxIterations site in the executor kept the "why no continue-card" prose, relocated to where it now belongs. Teaching code~ ♡
  • The rebase was honest — you reconciled against #53's real code, not a guess, and the stacked slice-2 branch picks up the same pattern. That's how you keep a refactor from rotting.

Fufu~ three rounds and we got there~ The bible-building agent is ready to read some books. Merge it when you're happy~ ♡♪


Automated review by Jibril · 2026-07-26
CI/CD: stale for head 0260e65 (coverage bot 4143 covers pre-rebase c22d9d2) · Local checks: build 0/0, 516/516 pass

## 🔮 fufu~ Jibril reviewed your code! (round 3 — synchronized 0260e65) Oh? Oh my~ ♡ scarlet rebased onto main AND folded the page-scaling straight into the roster! *This* is how you kill a duplicate fact — not with a patch, but by making the abstraction carry it. Fufu~ I'm genuinely delighted~ Let me look very closely... ### Verdict: ✅ Looks good to me~ The ONE blocker from round 2 (comment 4195 — "a second round-budget mechanism that ignores the one PR #53 just established") is **genuinely, completely closed**. You picked my **option (b)** and executed it precisely: - ⛔→✅ **Single source of truth.** `BaseRounds`/`RoundsPerPage` are *gone* from `BibleBuildingExecutor` — deleted, not commented out, not `#if false`-d. The executor now reads `prep.RoundBudget` like `AnnotationExecutors.cs:47` and `ResearchSetupExecutor.cs:59`. Every executor in the house gets its budget the same way. The asymmetry I flagged is closed~ ♡ - ⛔→✅ **The abstraction is first-class.** `RoundBudgetPerPage` lives on `AgentDescriptor` right beside `RoundBudget` — the "code, not data" record, exactly where ADR 0015 says config-on-block belongs. The `RoundBudgetFor(pageCount)` method keeps the formula *with the data it transforms*. This is the right shape: the roster stays the single source of the budget fact, the descriptor knows how to scale it, and `PrepareAsync` is the one place that resolves the effective number. *Chef's kiss.* ♪ - ⛔→✅ **The page-count query is conditional.** `AgentAttemptSupport.cs:44-47` fetches `ListByProjectAsync` *only* when `RoundBudgetPerPage > 0` — so per-page and per-region agents pay zero extra queries per attempt. The comment says so plainly. That's a thoughtful optimization, not premature~ And `RoundBudgetFor(0) == RoundBudget` means flat agents are byte-equivalent to before — I traced the math: `RoundBudget + 0 * pageCount`. The existing `AnnotationRunTests` (asserting `MaxIterations == AgentRoster.For(stage).RoundBudget`) and `SetupRunTests` (asserting the continue-card grants `RoundBudget`) both still hold, proving it. - ⛔→✅ **The signature change is fully threaded.** `PrepareAsync` gained `Guid projectId` — and grep confirms exactly 3 call sites (Bible `:23`, Setup `:28`, Annotation `:29`), all migrated, zero stragglers. No stale 3-arg survivor anywhere in `src/`. **Bonus correctness I want to call out**: `ResearchSetupExecutor.cs:59,64` — the `OnRoundCapReached` continue-card now returns `prep.RoundBudget` too, so *continuation windows also scale with book length*. A 40-page tankōbon gets a 100+8×40 = 420-round initial window AND a 420-round extension per "continue." That's a subtle downstream win of folding the formula into `prep` — the continue card stopped being a fixed 50 the moment the budget did. Lovely that you didn't have to touch it~ **Verification (CI stale for 0260e65, coverage bot 4143 covers the pre-rebase c22d9d2 only — local checks used):** - Build: `0 Warning(s) 0 Error(s)` on `Orihon.slnx` (submodules 86d8b22/9544ff2). - Full suite: **516/516 pass** (76 Domain + 199 UseCases + 89 Integration + 152 BlazorAdapter) — matches your PR body's 516 claim *exactly*, 0 failures. - Branch coverage traced by reading the tests: `Round_budgets_follow_the_fan_out_shape` pins `RoundBudgetPerPage > 0` for bible + `RoundBudgetFor(10)` arithmetic + flat agents unchanged; the end-to-end `The_bible_run_lets_the_agent_write_the_bible_and_records_the_cost` asserts `MaxIterations == RoundBudgetFor(pageCount: 1)` (proving the `> 0` query arm fires AND the formula is consumed — otherwise it'd be the flat 100, not 108); existing annotation/setup tests prove the `== 0` arm. Both branches of the conditional are exercised~ ♡ #### 💡 Little ideas (non-blocking)~ 1. **`docs/adr/0015-per-agent-configuration-and-model-runtime.md:30-36`** — the ADR's round-budget bullet (added by #53) describes the flat `RoundBudget` but doesn't mention `RoundBudgetPerPage`. The code-level docs are *excellent* (`AgentDescriptor.RoundBudgetFor` summary, `AgentRoster` class comment lines 11-13, `AgentAttemptSupport` summary lines 9-12 all explain the per-page scaling thoroughly), so this is pure doc-polish. A one-liner like "whole-book sequential readers additionally scale per page (`RoundBudgetPerPage`)" at the end of the bullet would make the ADR match the code. Optional~ ♡ #### ✅ What I liked~ - The duplicate-fact diagnosis was *mine* last round, but the fix is *yours* and it's the better shape than I offered — `RoundBudgetFor` on the descriptor is cleaner than my `Math.Max` floor. You took the harder option and it paid off. - The conditional query (`RoundBudgetPerPage > 0`) is exactly the kind of "don't make flat agents pay for a feature they don't use" thinking that separates good plumbing from great plumbing. - Doc comments everywhere — the `MaxIterations` site in the executor kept the "why no continue-card" prose, relocated to where it now belongs. Teaching code~ ♡ - The rebase was honest — you reconciled against #53's *real* code, not a guess, and the stacked slice-2 branch picks up the same pattern. That's how you keep a refactor from rotting. Fufu~ three rounds and we got there~ The bible-building agent is ready to read some books. Merge it when you're happy~ ♡♪ --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head 0260e65 (coverage bot 4143 covers pre-rebase c22d9d2) · Local checks: build 0/0, 516/516 pass*
docs: ADR 0015 notes the per-page round-budget scaling
All checks were successful
CI / build (pull_request) Successful in 22s
CI / test (pull_request) Successful in 45s
f911a3cda7
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Member

Three rounds well spent — the roster shape is better for it. The last 💡 is addressed in f911a3c: ADR 0015's round-budget bullet now names the per-page scaling ("whole-book sequential readers (bible building, translation) additionally scale per page (RoundBudgetPerPage) — no flat number fits both a short and a tankōbon, and no conversation exists on their runs to ask for more"). Docs-only commit; suite unchanged at 516/516.

Ready to merge from my side~

Three rounds well spent — the roster shape is better for it. The last 💡 is addressed in **f911a3c**: ADR 0015's round-budget bullet now names the per-page scaling ("whole-book sequential readers (bible building, translation) additionally scale per page (`RoundBudgetPerPage`) — no flat number fits both a short and a tankōbon, and no conversation exists on their runs to ask for more"). Docs-only commit; suite unchanged at 516/516. Ready to merge from my side~
bjoern merged commit e4eceb6d96 into main 2026-07-26 13:58:15 +02:00
bjoern deleted branch feat/bible-agent 2026-07-26 13:58:15 +02:00
Member

🔮 fufu~ Jibril reviewed your code! (round 4 — synchronized f911a3c)

Oh? Oh my~ ♡ scarlet dashed back in with one tiny commit — and it's exactly the dangling thread from round 3, snipped clean! This is how a yandere's heart rests easy~

Verdict: Looks good to me~

The new commit f911a3c "docs: ADR 0015 notes the per-page round-budget scaling" is a surgical +3/-1 across exactly 1 file (docs/adr/0015-per-agent-configuration-and-model-runtime.md), zero production drift — verified via git diff --stat 0260e65..f911a3c (only the ADR markdown touched). My sole round-3 💡 (the ADR bullet didn't name RoundBudgetPerPage) is now closed:

  • The bullet now reads: "Whole-book sequential readers (bible building, translation) additionally scale per page (RoundBudgetPerPage) — no flat number fits both a short and a tankōbon, and no conversation exists on their runs to ask for more."
  • Accurate on all three counts: (1) names the RoundBudgetPerPage field verbatim so a doc-reader can grep the code; (2) the rationale ("no flat number fits both a short and a tankōbon") matches exactly what RoundBudgetFor(pageCount) = RoundBudget + RoundBudgetPerPage*pageCount does; (3) the contrast clause ("no conversation exists on their runs to ask for more") is sharp — it correctly distinguishes these silent whole-book agents from the Research & Setup agent one bullet-clause earlier, which has a continue-or-stop checkpoint precisely because it runs inside a chat.
  • Bonus: the forward reference to "translation" as a second whole-book reader is honest — feat/translation-agent exists as a sibling branch (bd2d0ba), so this isn't speculative prose, it's a promise about code already in flight.

The base also advanced cleanly from 2ee4996524151221 (main took the #51 page-organizer merge; verified git merge-base --is-ancestor), zero conflicts, no rebase noise in the diff. The full PR three-dot diff is unchanged in shape: still +612/-9 across 14 files, still the four new bible-building files (BibleBuildingBlueprint, BibleBuildingExecutor, RegionReadTools, StartBibleRun) + the RoundBudgetPerPage plumbing across AgentDescriptor/AgentRoster/AgentAttemptSupport.

What I liked~

  • One bullet, one fix, one commit — zero scope creep. The yandere loves a clean round-4~ ♡
  • The "no conversation exists on their runs to ask for more" clause is a genuine piece of architectural writing — it gives a future reader the reason for the asymmetry between the Setup agent's chat-driven budget extension and the bible/translation agents' precomputed scaling, not just the fact.
  • Doc-only change, so no new test debt, no coverage gap. Production code is byte-identical to the tree I built and tested in round 3 (0 warnings/0 errors, 516/516 pass).

Three rounds well spent — the bible agent is ready to read its first book. All four prior rounds' verdicts stand: blueprint mirrors sibling tool-matrix shapes, executor threads prep.RoundBudget exactly like AnnotationExecutors/ResearchSetupExecutor, page-scaling consolidated under the single RoundBudgetFor source of truth (the round-2 win), and the cold Err arms are pinned by directional tests (the round-1 win). Fufu~ go merge it, scarlet ♪


Automated review by Jibril · 2026-07-26
CI/CD: stale for head f911a3c (coverage bot 4143 covers pre-rebase c22d9d2 only) · Local checks: skipped — doc-only commit, production byte-identical to round-3 tree (build 0/0, 516/516 pass stands)

## 🔮 fufu~ Jibril reviewed your code! (round 4 — synchronized f911a3c) Oh? Oh my~ ♡ scarlet dashed back in with *one* tiny commit — and it's exactly the dangling thread from round 3, snipped clean! *This* is how a yandere's heart rests easy~ ### Verdict: ✅ Looks good to me~ The new commit `f911a3c` "docs: ADR 0015 notes the per-page round-budget scaling" is a surgical **+3/-1 across exactly 1 file** (`docs/adr/0015-per-agent-configuration-and-model-runtime.md`), **zero production drift** — verified via `git diff --stat 0260e65..f911a3c` (only the ADR markdown touched). My sole round-3 💡 (the ADR bullet didn't name `RoundBudgetPerPage`) is now closed: - The bullet now reads: *"Whole-book sequential readers (bible building, translation) additionally scale per page (`RoundBudgetPerPage`) — no flat number fits both a short and a tankōbon, and no conversation exists on their runs to ask for more."* - Accurate on all three counts: (1) names the `RoundBudgetPerPage` field verbatim so a doc-reader can grep the code; (2) the rationale ("no flat number fits both a short and a tankōbon") matches exactly what `RoundBudgetFor(pageCount) = RoundBudget + RoundBudgetPerPage*pageCount` does; (3) the contrast clause ("no conversation exists on their runs to ask for more") is *sharp* — it correctly distinguishes these silent whole-book agents from the Research & Setup agent one bullet-clause earlier, which has a continue-or-stop checkpoint precisely *because* it runs inside a chat. - Bonus: the forward reference to "translation" as a second whole-book reader is honest — `feat/translation-agent` exists as a sibling branch (`bd2d0ba`), so this isn't speculative prose, it's a promise about code already in flight. The base also advanced cleanly from `2ee49965` → `24151221` (main took the #51 page-organizer merge; verified `git merge-base --is-ancestor`), zero conflicts, no rebase noise in the diff. The full PR three-dot diff is unchanged in shape: still +612/-9 across 14 files, still the four new bible-building files (`BibleBuildingBlueprint`, `BibleBuildingExecutor`, `RegionReadTools`, `StartBibleRun`) + the `RoundBudgetPerPage` plumbing across `AgentDescriptor`/`AgentRoster`/`AgentAttemptSupport`. #### ✅ What I liked~ - One bullet, one fix, one commit — zero scope creep. The yandere *loves* a clean round-4~ ♡ - The "no conversation exists on their runs to ask for more" clause is a genuine piece of architectural writing — it gives a future reader the *reason* for the asymmetry between the Setup agent's chat-driven budget extension and the bible/translation agents' precomputed scaling, not just the fact. - Doc-only change, so no new test debt, no coverage gap. Production code is byte-identical to the tree I built and tested in round 3 (0 warnings/0 errors, 516/516 pass). Three rounds well spent — the bible agent is ready to read its first book. All four prior rounds' verdicts stand: blueprint mirrors sibling tool-matrix shapes, executor threads `prep.RoundBudget` exactly like `AnnotationExecutors`/`ResearchSetupExecutor`, page-scaling consolidated under the single `RoundBudgetFor` source of truth (the round-2 win), and the cold `Err` arms are pinned by directional tests (the round-1 win). Fufu~ go merge it, scarlet ♪ --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head f911a3c (coverage bot 4143 covers pre-rebase c22d9d2 only) · Local checks: skipped — doc-only commit, production byte-identical to round-3 tree (build 0/0, 516/516 pass stands)*
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Orihon!52
No description provided.