Skip to content

Enhance validators and agent defaults with global config support#10

Merged
fuseraft merged 14 commits into
mainfrom
bugfix/brownfield
May 12, 2026
Merged

Enhance validators and agent defaults with global config support#10
fuseraft merged 14 commits into
mainfrom
bugfix/brownfield

Conversation

@fuseraft
Copy link
Copy Markdown
Owner

No description provided.

Scott Stauffer added 14 commits May 11, 2026 11:56
…dator

patch_file is a legitimate file-writing tool call (surgical edit) but was
invisible to HandoffToTesterValidator, causing agents that use patch_file
to be permanently blocked at the handoff gate. Also generalized the error
message away from "HANDOFF TO TESTER blocked" and Developer-specific build
instructions — this validator backs RequireWriteFile which is used on edges
beyond Developer→Tester (e.g. Archaeologist's RECON COMPLETE edge), so the
old message misdirected agents into irrelevant recovery steps.
Agents generated by templates had no Endpoint or ApiKeyEnvVar, forcing
users to manually add them to every agent file even when ~/.fuseraft/config
already declared a provider URL. ApplyGlobalDefaults now runs after config
binding (in both BuildAsync and LoadConfig) and fills in missing Endpoint
and ApiKeyEnvVar on every agent model, named alias, and selection/magentic
model. Explicit per-agent values always win; global values only fill empty
fields. Also adds ApiKeyEnvVar to UserConfig and UserConfigStore so it can
be set once in the global config and inherited everywhere.
…yEnvVar

When a user stores their provider key in the OS keychain (via `fuseraft key
set`) and no agent file declares ApiKey or ApiKeyEnvVar, the key was silently
unused at run time and auth failed. ApplyKeychainKeyAsync now runs after
ApplyGlobalDefaults in BuildAsync: it retrieves the keychain key once and
injects it as a literal ApiKey on every model config that still has neither
field set. Models with either field already populated are untouched. LoadConfig
(used for display and validation) is deliberately kept sync and unchanged since
the literal key is only needed at runtime, not for config inspection.
validators.md — RequireWriteFile now accepts patch_file alongside write_file;
update the "used on" header (no longer specific to HANDOFF TO TESTER), the
pass/fail descriptions, the ShellFallbackPattern prose, and the error-message
example to match the new generic message.

models.md — add "Global config defaults" section documenting that endpoint and
apiKeyEnvVar in ~/.fuseraft/config are applied to any agent model that omits
them, and documenting the full auth priority chain (explicit ApiKey → ApiKeyEnvVar
→ global apiKeyEnvVar → OS keychain). Update the ModelConfig table rows for
Endpoint and ApiKeyEnvVar to note the global-config fallback.

cli-reference.md — note that the --endpoint flag value is also inherited by
agents at run time; clarify that keychain-only agents skip the ApiKeyEnvVar
env-var check in static validation.
…v-independent

LoadConfig is used for static inspection and validation — it should check the
config as-written, not as-runtime-resolved. Applying global defaults there made
ValidateConfigCommand pass configs that are actually incomplete (e.g. unknown
model prefix with no Endpoint), because ~/.fuseraft/config's endpoint silently
filled the gap. Global defaults now apply only in BuildAsync where agents are
actually invoked. Fixes ModelWithoutPrefix_NoEndpoint_Errors test.
…th set

The short-circuit that bypasses model-ID prefix detection only fired when both
Endpoint and a literal ApiKey were present. When global config supplied Endpoint
and ApiKeyEnvVar (env-var reference instead of literal key), the model fell
through to DetectFromPrefix, which doesn't recognise AWS Bedrock-style IDs like
anthropic.claude-sonnet-4-5-20250929-v1:0, throwing "Cannot determine the LLM
provider". Extend the short-circuit to cover any form of auth (ApiKey OR
ApiKeyEnvVar) — if the caller supplied their own endpoint and auth, treat as
OpenAI-compatible and skip prefix detection entirely.
…tection fails

When a model ID doesn't match any known prefix (e.g. AWS Bedrock-style IDs like
anthropic.claude-sonnet-4-5-20250929-v1:0 used through Open WebUI), and an
Endpoint is set (either inline or injected from ~/.fuseraft/config), Resolve
was still throwing "Cannot determine the LLM provider". A custom Endpoint is
an unambiguous signal that the caller knows which provider to use — fall back
to openai-compatible instead of throwing, consistent with how the REPL wizard
treats enterprise/custom endpoints.
… it blank

ApplyGlobalDefaults already propagated Endpoint and ApiKeyEnvVar from the
global config; extend it to cover ModelId too. This completes the contract:
fuseraft run will not fail due to a missing connection field as long as
~/.fuseraft/config supplies the defaults — agent files only need to override
what differs from the global config (e.g. a per-agent MaxTokens or a
different model for one role).
… in SDK

ApiKeyCredential throws "Value cannot be an empty string" when given an empty
key, producing a cryptic stack trace from deep in System.ClientModel. Guard
both the openai-compatible and azure branches with an explicit check before
the constructor call, surfacing a message that names the model, the endpoint,
and the two ways to fix it (fuseraft key set or apiKeyEnvVar in global config).
…c catch in SessionRunner

The generic catch block called AnsiConsole.WriteException (printing the full
stack trace) and also set errorMessage, which RenderSummary then printed again
as '✗ Error: ...'. The message already surfaces clearly through RenderSummary;
the stack trace added noise without value for user-facing operational errors.
Add docs/context-management.md — a unified overview of all four context
layers (context store, persistent memory, ContextWindow filtering,
compaction). Covers TextOnly, ExcludeAgents, MaxTurnAge, MaxTailMessages,
ContextCapFraction, replay truncation, all five compaction modes,
IncludeReasoning, IncludeSymbolGraph, change log grounding, and the
head+tail pinning behaviour. Includes a flow diagram and opinionated
strategy guide.

Rename docs/context.md → docs/context-store.md so the file name matches
what it actually documents (the file-import CLI, not conversation context).
Update mkdocs.yml nav and index.md guide table accordingly.
…ontext import

Add DocumentTextExtractor (PDF via PdfPig, DOCX/PPTX/XLSX via
DocumentFormat.OpenXml) — fully cross-platform pure-.NET extraction,
no shell tools required.

Add DocumentPlugin with four read-only tools:
  document_extract_text — full text from PDF, DOCX, PPTX, or XLSX
  document_get_info     — format/size metadata without full extraction
  document_list_sheets  — sheet names from an XLSX workbook
  document_get_sheet    — single sheet as pipe-delimited text table

Fix ContextStore.AddAsync: binary documents (.pdf, .docx, .pptx, .xlsx)
are now extracted to .txt at import time so agents can access them via
read_file. Falls back to storing the binary with a warning on extraction
failure. ExtractionInfo recorded on ContextItem; notes surfaced in CLI
output.

Register Document in PluginRegistry (default + sandboxed); add capability
entries (all read) to PluginCapabilityMap. Update plugins.md and
context-store.md.
… Document plugin

- configuration.md: add Document to the capability tag table
- cli-reference.md: document binary extraction behaviour in fuseraft context add
- context-management.md: update Layer 1 to mention document formats and cross-link Document plugin
@fuseraft fuseraft merged commit f11e20e into main May 12, 2026
10 checks passed
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