Refactor/post grill#51
Merged
Merged
Conversation
added 30 commits
July 10, 2026 00:35
- Magentic's manager received raw participant transcript text despite the documented two-history invariant; added an isolated summarization call so managerHistory only ever contains LLM-generated summaries - GraphOrchestrator's BFS-layer back-edge classification misclassified forward edges as back-edges on diamond-shaped convergence, causing spurious phase-restarts; replaced with DFS-based ancestry classification - StructuredSelectionStrategy bypassed the shared FailureClassifier/ FailureHandlingConfig pipeline entirely; routed it through the same path Keyword/StateMachine strategies use - git_rebase and git_is_inside_work_tree were absent from the plugin capability map, so a Git:["read"]-restricted agent could still rebase; added entries plus a coverage test guarding future gaps - fuseraft validate-config rejected valid sub-graph node configs with a false "Agent is required" error; ported the SubGraphId validation branch from OrchestratorBuilder - RunCommand duplicated a stripped-down, less-safe copy of CompactionCoordinator's pre-loop compaction path; routed it through the same TryTriggerCompactionAsync used mid-loop - Assorted smaller correctness fixes: parallel-branch TurnIndex collisions, PinLastRoutingSignal defaulting to false, MergeStrategy.Benchmark and the sync Merge() overload silently degrading instead of failing loudly, dead compaction-detection code, EventEmitter dropping its CancellationToken, ChatClientFactory never being disposed, AdversarialOrchestrator's unused IHumanApprovalService parameter - Corrected every doc-drift finding in docs/design.md and docs/strategies.md (phantom Anthropic package dependency, missing WorkflowOrchestrator section, stale validator/hook/interface lists, inverted AND/OR termination semantics, unimplemented cost tracking, and more) - God-object decomposition and de-duplication refactors flagged in the review are intentionally deferred (see FINDINGS.md status lines) — this pass is scoped to correctness bugs, security gaps, and doc accuracy
- Extract JsonFileStore<T> for the load/reset-on-corrupt/locked-write pattern reimplemented independently in ChangeTracker (x2), IntentLog, FileVersionStore, and EvidenceStore; a correctness fix now only needs applying once instead of five times. - Extract FanOutHelpers (BuildContext, InvokeAgentAsync, MakeMessage, FireTokenBudgetWarning, FlushChangeTrackerAsync) shared by MapReduce, ScatterGather, and Adversarial orchestrators; AgentOrchestrator's differently-shaped fan-out is deliberately left alone. - Extract ValidatorRegistry.BuildValidatorsFromNames shared by GraphOrchestrator/WorkflowOrchestrator; StrategyFactory.BuildValidators stays separate since it needs different parameters. - Collapse CreateOrchestrator/ValidateAndSelectStrategy's 7 shared bool params into one OrchestratorKindFlags record, removing the risk of a silent positional-argument swap routing a session to the wrong orchestrator. - Fix StateMachineSelectionStrategy's threshold-escalation check, found while designing the Keyword/StateMachine unification: it only escalated on FailureAction.Abort, silently making Threshold dead for every type defaulting to Reinstruct (MissingEvidence, InvalidTransition, ConflictingEvidence all do). Now escalates on Threshold regardless of action, matching KeywordSelectionStrategy. - Update FINDINGS.md status lines and the resolution-status legend to reflect this second pass; a full shared Keyword/StateMachine FailurePipeline helper was investigated and deliberately not built — the genuine per-strategy differences (rate limiter, contract-failure backstop, verifier scheduling) made a forced shared helper riskier than the direct bug fix.
- WorkflowOrchestrator never wrapped agent calls with the circuit breaker or fed the unified context-assembly pipeline; it now mirrors GraphOrchestrator's pipeline-or-legacy-filter behavior and records governance violations (audit + rate-limit + SLO) on validator failure - MapReduce/ScatterGather agents ran with no memory/knowledge context; every Splitter/Mapper/Reducer/Participant/Synthesizer call now goes through FanOutHelpers.AssembleContextAsync with a fallback to raw instructions+history when no pipeline is configured - CorrectionEngine's reviewer-type inference was a magic-keyword match on "APPROVED"; GraphNodeConfig.ReviewerType is now an explicit node-level flag so workflow authors can name their decision keyword anything and still get the specialized reviewer-correction path
- GraphOrchestrator.cs owned 10 separate topology fields (back-edges, edges-by-source, route tables, unconditional routing maps, parallel groups) populated by 8 tightly-coupled private methods, one piece of the god-object decomposition tracked in PLAN.md - Move ComputeBackEdges/EdgeKey/BuildRouteTableForNode/WireBackEdges/ AssignParallelGroups/BuildValidatorsFromNames/ValidateParallelConfig/ DetermineStartNodeId/ParallelGroup into a new GraphTopology DTO class (src/Orchestration/Graph/), built once per StreamAsync call and treated as read-only afterward — collapses 10 fields into one _topology reference - TerminalSentinel/BranchTurnIndexStride become internal const so the new collaborator (and the parallel/sub-graph ones still to come) can reference them directly, mirroring the existing precedent of CorrectionEngine reaching into GraphOrchestrator.DefaultMaxRetries
- RecordAndEmitAsync/RunValidatorsAsync/InvokeRecoveryAgentAsync/ EmitAndInjectValidationFailureAsync/RecordGovernanceViolation/ ApplyHumanApprovalGateAsync/EmitContextWindowWarnAsync/ EmitContextAssemblyAsync/PersistCorrectionsAsync were private instance methods on GraphOrchestrator, but RunParallelNodeAsync (moving to its own collaborator next) needs the identical logic — moving parallel fan-out without these would force either duplicating them or leaving them stuck as private instance methods invisible to a separate class - Move them into a new internal static TurnExecutionHelpers class, explicit-parameter style mirroring the existing CorrectionEngine/ KeywordDetector pattern, bundled behind one TurnServices record (mirrors the OrchestratorKindFlags bundling precedent in OrchestratorBuilder.cs) so call sites take one services param instead of 8-10 loose ones - _services is a lazily-computed property, not a field initializer — its AgentStarting/TokenBudgetWarning forwarding lambdas reference other instance members, which C# field initializers cannot do (CS0236); a property getter runs after construction completes
- RunSubGraphNodeAsync builds and streams a nested sibling orchestrator (GraphOrchestrator/MapReduce/ScatterGather) for SubGraphId nodes — one of the 8-10 orthogonal responsibilities GraphOrchestrator owned directly, per PLAN.md's god-object decomposition list - Move it verbatim into a new SubGraphExecutor class, taking the same TurnServices bundle as TurnExecutionHelpers plus loggerFactory (needed to build sibling-orchestrator-specific loggers, not itself a "turn execution" concern so kept out of TurnServices) - TurnServices.Logger widens from ILogger to ILogger<GraphOrchestrator> since the recursive `new GraphOrchestrator(...)` sub-graph case needs the generic-typed logger its constructor requires - GraphOrchestrator's own _subGraphExecutor field is a lazy property, not a field initializer, for the same CS0236 reason _services is — it depends on the _services property, itself a non-static member
- RunParallelNodeAsync/ForkContext/MergeParallelContexts plus a ~108-line inline fan-out dispatch block inside RunNodeExecutorAsync were GraphOrchestrator's parallel-execution responsibility — the most entangled of the god-object's pieces, since the per-branch retry loop shares response-recording/validator/recovery-agent logic with the sequential back-edge/forward-edge turn loop - Move it into a new ParallelFanOutExecutor class built on top of the TurnExecutionHelpers/TurnServices extracted in the prior two commits — RunFanOutAsync wraps the inline dispatch block (validator run → HITL gate → fork → concurrent branches → merge → dispatch), RunSingleBranchAsync is the former RunParallelNodeAsync body - The inline dispatch block in RunNodeExecutorAsync collapses from ~108 lines to a single _parallelFanOut.RunFanOutAsync(...) call returning the same (shouldReturn, consecutiveFails) shape already used by HandleBackEdgeAsync/EvaluateRouteAsync - GraphOrchestratorParallelTests.cs's ForkContext/MergeParallelContexts call sites move from GraphOrchestrator to ParallelFanOutExecutor — mechanical qualifier rename only, no test behavior changed since those tests never construct a GraphOrchestrator instance
- RunNodeExecutorAsync's unconditional (no-keyword) routing branch was a ~90-line inline block covering two sub-cases (auto-forward, auto-back-edge) plus a config-gap fallthrough — the last of PLAN.md's explicit RunNodeExecutorAsync asks: extract the unconditional-routing and parallel-fan-out blocks into named methods, continuing the pattern already used for HandleBackEdgeAsync/EvaluateRouteAsync - Unlike the topology/turn-helpers/sub-graph/parallel extractions, this one stays a same-class private method rather than a new collaborator — it's tightly coupled to the main turn loop's control flow (three-way handled/shouldReturn/fallthrough branching) with no reuse pressure from elsewhere, so a new class would add indirection without buying separation - RunNodeExecutorAsync shrinks from ~400 lines to 227, now reading as setup + turn-loop head + terminal validation + HandleUnconditionalRoutingAsync + keyword detection + HandleBackEdgeAsync + ParallelFanOutExecutor.RunFanOutAsync + EvaluateRouteAsync + BLOCKED check + final correction — every block either inline glue or a one-line delegate call
- Update GraphOrchestrator's class-level doc comment to describe the four new Graph/ collaborators (GraphTopology, TurnExecutionHelpers, SubGraphExecutor, ParallelFanOutExecutor) extracted over the prior five commits, and fix a stale RecordAndEmitAsync cref that moved - Drop the now-redundant _humanApprovalService field — _services (TurnServices) already captures the same constructor parameter, and the dual capture triggered CS9124; the two remaining null-checks now read _services.HumanApprovalService directly
- CreateOrchestrator took 21 in-params + 1 out-param, the literal form of PLAN.md's "27-parameter list" finding (27 is the pre-OrchestratorKindFlags-collapse-equivalent count, not the current signature — worth correcting for future reference) - Bundle into three records mirroring the existing OrchestratorKindFlags precedent: OrchestratorInfraServices (8 shared-infrastructure params threaded into AgentFactory/StrategyFactory and nearly every orchestrator kind's ctor), OrchestratorKnowledgeServices (5 params feeding ContextBroker/ContextAssembler/ContextAssemblyPipeline construction), OrchestratorSessionPaths (4 path/identity params). humanApprovalService stays standalone — it's used both unconditionally (StrategyFactory) and conditionally gated by flags.HitlMode, so folding it into a bundle would obscure that - Drop knowledgeSandbox — dead parameter, never referenced anywhere in CreateOrchestrator's body - Replace the out repoMemoryExtractor parameter with a tuple return, matching the file's existing convention of returning records (OrchestratorBuildResult, InfrastructureResult) rather than out-params - New signature: 6 params + tuple return, down from 22. CreateOrchestrator is private with its only call site in BuildAsync (same file), so this is zero-blast-radius outside this file
- BuildSystemPrompt and its 7 helper methods (ResolveBasePrompt, BuildTestSelectorBlock, BuildProjectRootBlock, BuildPluginArtifacts, BuildGitIgnoreBlock, BuildConventionBlock, AppendList) are pure prompt-assembly logic, not orchestrator construction — part of PLAN.md's "file's broader god-object shape" finding beyond the CreateOrchestrator parameter list - All 8 are pure functions of their params with a single caller chain (only BuildAsync calls BuildSystemPrompt; everything else in this group is called only from within it) — zero external callers, so this is a pure internal move with a one-line call-site update - BrownfieldJsonOpts widens from private to internal — shared between OrchestratorBuilder's remaining pipeline methods and the new SystemPromptBuilder (and will be needed again by OrchestratorConfigLoader next), mirroring how GraphOrchestrator's constants were widened for its own collaborator split
- LoadAndExpandConfig/LoadConfig/LoadSecurityConfig/ApplyGlobalDefaults/ ApplyKeychainKeyAsync/BindConfig/ResolveAgentFiles/LoadAgentFile/ MergeAgentConfig/ExpandEnvVars/InterpolateSessionId/ ValidateSchemaVersion/ResolveSandboxPath + the VsCodeMode static flag are config loading/binding/pre-processing — a responsibility distinct from orchestrator construction, and the largest piece of PLAN.md's "file's broader god-object shape" finding - Several of these (LoadConfig, LoadSecurityConfig, InterpolateSessionId, VsCodeMode) are called from other CLI commands beyond BuildAsync — update the 6 external call sites (Program.cs, ModelsCommand.cs, ReplCommand.cs, RunCommand.cs x2, ShowConfigCommand.cs x2, ValidateConfigCommand.cs x2) plus OrchestratorBuilder's own remaining internal call sites (BuildAsync's LoadAndExpandConfig call, ResolveSecurityConfig's 6 ResolveSandboxPath calls) - ResolveAlias/ValidateApiKeysAsync deliberately NOT moved here — they go to a separate ApiKeyValidator collaborator next, since their responsibility (provider connectivity probing) is distinct from config loading despite living in the same file today
- ValidateApiKeysAsync + ResolveAlias + the shared _validationHttp client are provider-connectivity probing, distinct from both config loading (OrchestratorConfigLoader, previous commit) and orchestrator construction — the last piece of PLAN.md's "file's broader god-object shape" finding - ResolveAlias's only caller is ValidateApiKeysAsync (confirmed via research), so it moves here rather than to OrchestratorConfigLoader despite superficially looking like a config-resolution helper - Update the 2 external call sites (RunCommand.cs, EvalCommand.cs)
Update OrchestratorBuilder's class-level doc comment to describe the three new Cli/ collaborators (OrchestratorConfigLoader, SystemPromptBuilder, ApiKeyValidator) extracted over the prior three commits.
- TruncateIntermediateAssistantReasoning/CompressSupersededShellPairs/ DropSupersededObservationalPairs/DropSupersededWritePairs/ KeepLastToolPairs/TrimInTurnContext/EstimateContentChars were already internal static with explicit params and no AgentFactory instance- state dependency — a quasi-public surface already independently consumed by ReplFactory.cs (5 methods, 10 call sites) and 3 more Repl files (EstimateContentChars), duplicating the same 6-step filter sequence that also appears twice inside AgentFactory's own non-streaming/streaming middleware paths - Move verbatim into a new AgentContextCompactionFilters class — mirrors the TurnExecutionHelpers precedent from the GraphOrchestrator decomposition (explicit-parameter internal static class). Extracted first because the next two AgentFactory god-object pieces (tool resolution, middleware chain) don't depend on it, but a chat- client middleware collaborator planned next does - Update all external call sites (ReplFactory.cs, ReplSessionContext.cs, ReplTurn.cs, ReplCommands.Context.cs) and the dedicated test file (AgentFactoryKeepLastToolPairsTests.cs, 6 call sites) to the new qualifier — mechanical rename, no behavior change
- ConvertPluginTools/BuildCachingMiddleware/WrapWithNotifications/ BuildSubAgentTools resolve an agent's plugin declarations into AIFunctions (including the offload-caching and tool-call- notification wrapping layers) — single-caller-only from Create, low coupling to the rest of agent construction - Move into a new AgentToolResolver class constructed from AgentFactory's own primary-ctor params (chatClientFactory, pluginRegistry, securityConfig, scratchpadConfig, chatroomConfig, eventEmitter). ConvertPluginTools now takes sessionId plus the caller's turnResettables set/lock as explicit params instead of reading AgentFactory's fields directly, mirroring how GraphOrchestrator passed _recoveryActivated into ParallelFanOutExecutor rather than baking shared mutable state into a collaborator's constructor - _toolResolver is a plain field initializer (not the lazy-property workaround the next collaborator needs) — its constructor only closes over primary-constructor parameters, which field initializers are allowed to reference; CS0236 only blocks references to other instance members - Zero external call sites for this group (all methods were already private) — zero blast radius outside this file
- BuildMiddlewareChain/BuildEventEmitMiddleware/BuildGovernanceMiddleware plus the retry-escalation helpers (MergeOptions, IsContextLimitException, AdaptiveTrimMessages, TrimToolResultsToChars, DropAllToolContent, ProactivelyTrimIfNeeded, BuildInnerCallContextPayload, EnforceContextBudget, EnforcePayloadLimit, EstimateToolSchemaChars, HandoffWasInvoked, BuildChatOptions) composed the chat-client middleware chain and governance wrapping — single-caller-only from Create, built on top of AgentContextCompactionFilters for the always-on per-turn filter pipeline - Move into a new AgentMiddlewareBuilder class constructed from AgentFactory's own logger/changeTracker/securityConfig/ governanceKernel. This was the last of the three collaborator extractions — the whole "// Helpers" region below Create() moved, leaving AgentFactory.cs as just the public per-session surface plus Create()'s conductor body - _middlewareBuilder needs the lazy-property pattern (not a plain field initializer like _toolResolver) — its constructor takes _logger, an instance field rather than a primary-constructor parameter, so CS0236 applies the same way it did for GraphOrchestrator's _services/_subGraphExecutor/_parallelFanOut - AgentFactory.cs: 1682 -> 259 lines
Update AgentFactory's class-level doc comment to describe the three new Infrastructure/Agents/ collaborators (AgentToolResolver, AgentMiddlewareBuilder, AgentContextCompactionFilters) extracted over the prior three commits.
- RunSpinnerAsync/ClearSpinnerLine/WriteChunkSmoothAsync/SpinnerFrames/ StripAnsi take no ReplSessionContext and were already independently consumed by ReplCommands.Agents.cs (10 call sites, for /diagnose, /explore, /locate sub-agent commands) — unrelated to turn execution, the same "already quasi-public, deserves an honest home" shape as AgentContextCompactionFilters - Move verbatim into a new ReplConsole class. Update the 3 internal call sites (still inside ReplTurn.ExecuteAsync at this point) and the 10 external call sites in ReplCommands.Agents.cs - First of two new-file extractions for ReplTurn.cs's god-object decomposition; the file's dominant complexity (the 440-line ExecuteAsync) is a separate, same-class extraction next, since SessionRunner's precedent for it (PLAN.md's own recommendation) factors exception handling into named methods within the same class, not a new collaborator
- HandlePlanCapture/TryParsePlan/HandleStepResult/VerifyStepAsync/ RunVerifyCommandAsync process what happens to plan/step state once a turn's response is complete — narrow ReplSessionContext footprint, and VerifyStepAsync/RunVerifyCommandAsync already take no ctx parameter at all, the same "most self-contained" shape SubGraphExecutor had in the GraphOrchestrator decomposition - Move verbatim (including the InspectTools set, used only by these two methods — MutationTools is a separate set used only by ExecuteAsync's mutation-correction block, stays in ReplTurn.cs) into a new ReplTurnOutcome class. Update ExecuteAsync's 2 call sites; StepIterationLimit stays on ReplTurn (referenced by 5 unrelated external call sites building ctx.StepClient) and is read from the new file as ReplTurn.StepIterationLimit, mirroring the existing GraphOrchestrator.DefaultMaxRetries cross-class-constant precedent - Second of two new-file extractions; ExecuteAsync's dominant complexity (the retry/streaming loop) is a same-class extraction next, per SessionRunner's precedent
- ExecuteAsync's 440-line body owned turn streaming, retry/error classification, tool-call surface hints, plan capture, step verification, and event emission all in one method (PLAN.md's exact characterization) — its retry loop alone read and mutated ~10 method-local accumulators (sb, toolCallsThisTurn, fileChanges, token counters, capturedResults, spinner-control locals closed over by a local StopSpinnerAsync function) across 220 lines - Extract the spinner setup, while(true) retry loop with its 3 catch arms, and post-loop response materialization into a new same-class StreamTurnResponseAsync method returning a TurnStreamResult record struct — mirrors SessionRunner.HandlerOutcome's shape exactly, per PLAN.md's explicit recommendation to mirror that precedent. This wasn't just relocating the complexity: collapsing 10 mutable locals into one immutable result actually removes it from ExecuteAsync's scope, rather than just moving the same tangle elsewhere - Not a new collaborator class — the retry loop's tight coupling to its own local accumulators made a separate-class extraction a leaky-abstraction risk (ref params or a mutable accumulator object threaded through), the same kind of tradeoff PLAN.md's "Partial" section already judged worse than the current shape in other cases - ExecuteAsync shrinks from ~440 lines to ~230
- The mutation-claim-correction and adversarial-critic-review blocks
were two structurally-identical inline chunks in ExecuteAsync
("build a correction message → recursively re-enter ExecuteAsync"),
unlike SessionRunner's uniformly-named handler shape
- Extract into TryApplyMutationCorrectionAsync and
TryApplyCriticReviewAsync — same-class named methods, verbatim
bodies, giving each recursive-correction path an identity
- Last of the ReplTurn.cs same-class extractions; ExecuteAsync is now
~190 lines (down from 440 originally)
Add ReplTurn's class-level doc comment describing the two new collaborators (ReplConsole, ReplTurnOutcome) and the same-class StreamTurnResponseAsync extraction from the prior four commits.
- First step of the FileSystemPlugin.cs god-object decomposition (fifth item in PLAN.md, aggressive scope): pulls ResolveSafe, SummaryPath, InvalidatePathAsync, and StreamPreviewLinesAsync into a stateless static class, taking former field reads as explicit parameters - These four are cross-cutting utilities used by every pipeline (read/patch/write, and the directory/inspection tools moving to FileSystemManagementOps in a later step) — making them stateless now is what makes splitting the tool surface across two classes possible - InvalidatePathAsync takes the caller's per-turn HashSet<string> instances by reference rather than owning them, mirroring how GraphOrchestrator passed _recoveryActivated into ParallelFanOutExecutor
- Second step of the FileSystemPlugin.cs decomposition: moves the 8 patch/write pure helpers (CountLines, NormalizePatchText, ExtractExcerpt, FindFirstMismatchingLine, Truncate, EnsureFileExistsAsync, ComputeAndReportDiff, FindTypographicChars) plus their 4 static lookup tables into a stateless class - QuoteNormalizeExtensions is shared by both PatchFileAsync and WriteFileAsync, which is why the patch and write helper groups stay together in one file rather than being split further - Fixed a transcription bug caught by the test suite: the non-breaking space key in TypographicCharNames was silently normalized to a regular ASCII space while authoring the new file, which made the typographic-character write guard fire on every source file
- Third step of the FileSystemPlugin.cs decomposition: changes the internal storage from one object per plugin name to a list, adding RegisterAdditional (append) and TryGetAll (resolve every object) alongside the existing Register/TryGet (replace/resolve-first) - Purely additive — every existing plugin still resolves to a list of one, so this is behavior-preserving until something calls RegisterAdditional/TryGetAll, which the next step (splitting FileSystemPlugin's tool surface into a second registered object) will be the first to do - Adds "FileSystemManagementOps" to NoPrefixPlugins ahead of that next step, so its reflected tool names come out unprefixed
- Fourth (coupled) step of the FileSystemPlugin.cs decomposition: moves the 12 stateless directory-management and read-only inspection tools (list_files, delete_file, get_file_info, set_permissions, create_directory, delete_directory, copy_file, move_file, grep_file, get_file_summary, save_file_summary, list_directory) to a new FileSystemManagementOps, registered as a second object under "FileSystem" via PluginRegistry.RegisterAdditional - FileSystemManagementOps borrows FileSystemPlugin's per-turn read/write/patch HashSets by reference (via 3 new internal accessors) so InvalidatePathAsync calls from either object clear entries the other one added, and a single BeginTurn() resets both — it does not implement ITurnResettable itself since it owns no state - Updates every call site that resolves "FileSystem"'s full tool set to use the new TryGetAll (AgentToolResolver's two branches, ReplCommand's --no-tools tool list, PluginsCommand's display/count) - Splits FileSystemPluginTests.cs: the ~24 call sites testing the moved methods move into a new FileSystemManagementOpsTests.cs, built around a paired _plugin/_ops fixture mirroring the production wiring, plus one new test asserting the cross-object invalidation invariant this design depends on (delete via _ops clears a read cached via _plugin) — this is the one net-new test, everything else is a straight relocation - PluginCapabilityMapCoverageTests.cs: FileSystem moves from the generic single-object theory data to its own fact covering both objects, mirroring how Graph already needed its own fact
- Fifth and final step: updates FileSystemPlugin's class-level doc comment to describe its narrowed scope (the read/patch/write pipeline) and its relationship to the three sibling collaborators (FileSystemManagementOps, FileSystemSandbox, FilePatchDiffing) - Confirmed end to end via `fuseraft plugins --plugin FileSystem`: all 15 tool names reflect unprefixed across both registered objects with no name collision
- ConversationCompactor.cs (1182 lines) bolted five orthogonal prefix-block
builders (brief, symbol graph, objectives, reasoning, exploration) onto its
mode-dispatch/trimming responsibility; the architecture review named this
split out specifically ("prefix-block construction into a separate
collaborator")
- moved ReadReasoningForRangeAsync/BuildReasoningBlock, BuildObjectiveBlockAsync,
BuildBriefBlockAsync, BuildSymbolGraphBlockAsync/BuildSymbolGraphText/
LoadAllChangedFilesAsync, BuildExplorationBlockAsync and its four helpers, and
CombineBlocks verbatim into a new instance collaborator constructed once with
the shared per-session paths/stores (changeLogPath, intentLog, eventsLogPath,
evidenceStore, objectiveManager, readCachePath, briefPath)
- the one call-varying value, the active session id (settable after
construction via SetSessionId), is threaded through BuildAsync as an explicit
parameter instead of a closed-over field, since the collaborator is
constructed before SetSessionId can be called
- 1182 -> 812 lines; new file 428 lines; 867/867 tests stay green (no test
covers ConversationCompactor directly, so correctness relied on verbatim
transcription plus the compiler catching any qualifier miss)
- The 6-step compaction filter sequence (drop superseded writes/reads, compress shell reruns, truncate reasoning, cap tool-pair window, trim char budget) was hand-copied across 4 call sites: both paths of AgentMiddlewareBuilder.BuildMiddlewareChain and both paths of ReplFactory.BuildClient, kept in sync only by convention. - New AgentContextCompactionFilters.ApplyInTurnFilters composes the sequence once, preserving each site's own skip-if-zero convention for the tool-pair/char-budget steps so behavior is unchanged (ReplFactory passes maxInTurnChars: 0, matching its prior never-trim behavior).
added 7 commits
July 10, 2026 23:35
- Events (EventEmitter, EventTypes) and ParallelAgentBatch are used by Infrastructure and Core code, forcing those layers to depend on Orchestration; relocating them into Core removes that inversion - drop the now-unnecessary `using fuseraft.Orchestration;` imports left behind in Infrastructure files and StateMachineSelectionStrategy - add src/Core/GlobalUsings.cs for the new fuseraft.Core.Events namespace
- TryGetGitBranch only redirected stdout, so a failed `git rev-parse` outside a repo printed git's "fatal: not a git repository" straight to the terminal before the REPL banner rendered
- fuseraft init gains --no-boilerplate to skip architecture.yaml and
knowledge/lifecycle.yaml for small/single-purpose projects that won't
use `fuseraft arch check` or `fuseraft knowledge gc`. Knowledge dirs
and .fuseraftignore are still scaffolded since other plugins depend
on them.
- Fixed the "Respected by" header in the generated .fuseraftignore,
which named three commands (fuseraft cleanup, fuseraft gc, fuseraft
archive-session) that don't exist — the real consumers are
`fuseraft sessions --cleanup` and `fuseraft knowledge gc --apply`.
- fuseraft knowledge gc --apply now also prunes ephemeral log files
under ~/.fuseraft/logs/{project_slug}/ (e.g. app.log,
repl_events.jsonl), matching the logs/** pattern in .fuseraftignore
that previously had no consumer.
- Preflight's instructions call git_is_inside_work_tree()/git_status(), but its Plugins list omitted Git, so those calls silently fell back to shell_run and failed with exit 128 outside a git repo - Project-type detection only recognized language-specific toolchain manifests (pyproject.toml, package.json, etc.), so tasks whose deliverable is a generic manifest.yaml always classified as "unknown" and needlessly probed every runtime - Applies to both greenfield and swe/devteam Preflight agents; greenfield's Planner manifest self-critique also updated to accept manifest.yaml
- The existing-file check only covered the main orchestration.yaml; confirming that one prompt silently clobbered every agents/*.yaml file underneath it with no separate warning, destroying any hand-edited agent config - init now checks all target paths (config + every agent file) up front, lists every conflict, and asks once before writing anything - --force skips the check entirely for scripted/CI regeneration
- Score() read only the last assistant message's plain-text Content, but a turn that just calls handoff() with no prose leaves Content empty — the routing keyword lives in the tool-call argument instead - RegexTerminationCondition already falls back to the handoff argument when text is empty, so a session could terminate correctly (APPROVED routed the Reviewer to Done) while eval scoring still reported "regex not matched: \bAPPROVED\b" on the same run - finalContent now folds in the last handoff call's ArgsSummary before running expect_keywords/expect_regex/forbidden_keywords, so scoring agrees with the orchestrator's own definition of the signal
- SessionRunner escalates to PromptValidatorStuckAsync/ PromptBlockerResolutionAsync unconditionally whenever a validator gets stuck or an agent reports BLOCKED, regardless of hitlMode — that safety net applies to plain runs too, not just --hitl sessions - EvalCommand wired up ConsoleHumanApprovalService anyway, which blocks on Console.ReadLine(). With no TTY attached (eval runs, CI) that read returns immediately as if Enter were pressed, so it "worked" by accident — but only after printing a prompt that could never be answered, reading as a hang in captured output - Added NonInteractiveHumanApprovalService: resolves every prompt to the same no-human-available outcome deterministically, with no console I/O and no dependency on EOF behavior. EvalCommand now uses it instead
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.