Skip to content

Refactor/post grill#51

Merged
fuseraft merged 37 commits into
mainfrom
refactor/post-grill
Jul 11, 2026
Merged

Refactor/post grill#51
fuseraft merged 37 commits into
mainfrom
refactor/post-grill

Conversation

@fuseraft

Copy link
Copy Markdown
Owner

No description provided.

Scott Stauffer 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).
Scott Stauffer 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
@fuseraft fuseraft merged commit 390a49c into main Jul 11, 2026
3 checks passed
@fuseraft fuseraft deleted the refactor/post-grill branch July 11, 2026 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant