docs/StrategyEngine/04-Preprocessors.md
Preprocessors run after the user chooses a Strategy and before the prompt is rendered for the chat model.
They are not part of condition matching. Matching decides whether a Strategy is recommended. Preprocessors prepare execution-time variables for the one Strategy the user actually executes.
v1 preprocessors must be predefined by Everywhere or trusted plugins. Strategy files can reference them by ID, but cannot define arbitrary executable code.
v1 preprocessors can:
StrategyContext.extra values already collected for matching when available.v1 preprocessors cannot:
ToolRulesets.The result shape should leave room for future patches, but v1 should only implement variables.
1. User sees recommended Strategies
2. User selects one Strategy
3. User optionally enters additional request text
4. User sends
5. Engine creates execution context
6. Engine runs preprocessors in declared order
7. Engine merges variables
8. Engine renders Strategy body/system prompt
9. Engine applies Strategy ToolRulesets
10. Engine creates UserStrategyChatMessage
11. ChatService generates response
This keeps the existing Everywhere interaction model: the Strategy can be selected, then the user can provide an optional argument. A future one-click Strategy may send immediately when it has no required user argument, but v1 should not require that UI behavior.
Existing objects:
UserStrategyChatMessage already stores Strategy and optional PreprocessorResult.ChatService already reads Strategy.ToolRulesets.ChatHistoryBuilder already calls RenderStrategyUserPrompt.ScopedPromptRenderer already supports {Argument} and preprocessor variables.Required changes:
UserStrategyChatMessage, or before rendering history if delayed execution is chosen.PreprocessorResult in UserStrategyChatMessage so retries/replays are stable.{Argument} behavior as a compatibility alias.Recommended interface:
public interface IStrategyPreprocessor
{
string Id { get; }
IDynamicResourceKey DisplayNameKey { get; }
IDynamicResourceKey DescriptionKey { get; }
IDynamicResourceKey PermissionDescriptionKey { get; }
Task<PreprocessorResult> ProcessAsync(
StrategyExecutionContext context,
CancellationToken cancellationToken = default);
}
The registry should support:
public interface IStrategyPreprocessorRegistry
{
bool TryGet(string id, out IStrategyPreprocessor preprocessor);
IReadOnlyList<IStrategyPreprocessor> GetAll();
}
Unknown preprocessor IDs are validation errors when possible. If a preprocessor disappears after validation, execution must fail with a user-readable error and must not send the prompt.
Execution context should contain the normalized Strategy, the current input context, and the user's optional argument.
public sealed record StrategyExecutionContext
{
public required Strategy Strategy { get; init; }
public required StrategyContext StrategyContext { get; init; }
public string? UserInput { get; init; }
public CancellationToken CancellationToken { get; init; }
}
If matching produced a StrategyCandidate, execution may reuse candidate diagnostics and collected extra. If context is stale, implementation may refresh required extra before preprocessing.
Recommended v1 result:
public sealed record PreprocessorResult
{
public IReadOnlyDictionary<string, object?> Variables { get; init; } =
new Dictionary<string, object?>();
public IReadOnlyList<StrategyDiagnostic> Diagnostics { get; init; } = [];
}
Variable keys must use path style:
preprocess.browser.url
preprocess.browser.readable_text
preprocess.file_manager.selection.text
preprocess.file_manager.selection.paths
Do not use PascalCase variables for new preprocessors. Existing variables may be supported as aliases during migration.
Sources available to prompt rendering:
attachments.*clipboard.*assistant.*environment.*extra.*preprocess.*{Argument}Recommended precedence:
preprocess.* exact key
extra.* exact key
attachments/clipboard/assistant/environment exact key
built-in prompt variables
compatibility aliases
Preprocessors should not write into extra.* or attachments.*; they should write into preprocess.*. The renderer can still expose context values directly to templates.
If duplicate keys are produced by multiple preprocessors:
Strategy body is Markdown with variable placeholders.
Please summarize:
{extra.file_manager.selection.items}
Additional request:
{Argument}
Rules:
{path.to.value}.{Argument} compatibility:
{Argument} resolves to the user input text.{input} or {user.input} only if those aliases are formally added later.{Argument} because the current renderer supports it.Current behavior appends user input under <UserRequestStart> when body and user input both exist. v1 may keep this for compatibility, but new rendering semantics should be explicit:
{Argument}, append user input in a clearly delimited section.{Argument}, do not append the user input a second time.Recommended appended section:
<UserRequestStart>
...
Do not change this marker in v1 unless the chat history builder is updated consistently.
Strategy may define systemPrompt.
systemPrompt: |
You are an expert legal summarizer.
Rules:
systemPrompt is null, use the assistant default.systemPrompt is present, pass it as system prompt override for this request.Strategy tool rules are applied for the request executing that Strategy.
Current layering in ChatService is close to:
default persistent tool settings
-> Strategy.ToolRulesets
-> ChatContext.ToolRulesets
v1 should preserve this behavior unless a separate plugin policy spec changes it.
Rules:
Preprocessors must expose permission description keys:
IDynamicResourceKey PermissionDescriptionKey { get; }
Strategy details UI should aggregate permissions from:
when context paths.extra providers inferred by matching.Examples:
Reads selected text
Reads clipboard text
Reads visible UI information
Reads file manager current folder and selection
Allows file read tool
Allows web search tool
v1 does not require a new consent prompt for every preprocessor, but the metadata must exist.
If any preprocessor fails:
Failure categories:
preprocessor.not_found
preprocessor.timeout
preprocessor.permission_unavailable
preprocessor.context_missing
preprocessor.exception
preprocessor.invalid_result
Timeout is failure during execution, not a silent non-match. Matching timeouts hide a Strategy; execution timeouts stop a user-requested action and explain why.
Preprocessors must honor cancellation tokens.
Cancellation sources:
Cancelled execution must not create a partially rendered prompt.
The exact list can be smaller in the first PR, but the design should support:
| ID | Purpose |
|---|---|
selected-text | Expose selected text as preprocess.selection.text. |
file-manager-selection | Expand file manager selection into paths/content summaries. |
browser-active-page | Expose URL/title and possibly readable text. |
clipboard-text | Expose clipboard text. |
visual-element-text | Extract text from the primary visual element. |
Each built-in preprocessor must have:
Execution diagnostics should be visible in Strategy diagnostics and logs.
Recommended fields:
strategy id
preprocessor id
duration
variables produced, names only
failure category
exception type, if any
Do not log full variable values by default.
The result type can later grow optional patches:
public sealed record PreprocessorResult
{
public IReadOnlyDictionary<string, object?> Variables { get; init; }
public ToolRulesets? ToolRulesetPatch { get; init; }
public IReadOnlyList<ChatAttachment>? AdditionalAttachments { get; init; }
public StrategyModelOptions? ModelOptionsPatch { get; init; }
}
These fields are intentionally out of v1 scope. Do not implement them until the matching and file format are stable.