Agentic Loop Module (WindowsAgenticLoop.Common.psm1)#
A reusable PowerShell module for the planner/executor agentic loop pattern. Lets any project set up an autonomous coding loop with:
A planner (expensive model, e.g. Claude Fable 5 or GLM 5.2) that analyzes the codebase and writes tasks to
BACKLOG.md.An executor (cheaper model, e.g. Claude Sonnet or DeepSeek v4 Flash) that drains the queue one task at a time, building and testing as it goes.
Builds, tests, and quality gates on configurable intervals, cycling through different build configurations.
A build fixer: when a periodic build fails, the executor-tier model is dispatched with the build log tail to fix it, and the loop stops after N consecutive unfixable failures.
Engines#
Two agent CLI backends are supported; select via config engine,
$env:AGENTIC_ENGINE, or the -Engine parameter of Invoke-AgenticLoop:
Engine |
Invocation |
Role prompts |
Permissions |
|---|---|---|---|
|
|
|
Configured in |
|
|
|
Planner sandboxed via |
Role prompts: one composition, both engines#
Both “Role prompts” cells above hold the same text whenever a prompt is configured. On every start the loop composes
shared/agentic-loop/system-prompts/<role>.md the shared, engine-agnostic role prompt
+ <your overlay>.md your project delta
and delivers it twice: to claude as a temp file behind
--append-system-prompt-file, and to opencode by writing
<repo>/.opencode/agents/<role>.md — opencode takes no prompt file on its
command line, so that file is its only channel.
.opencode/agents/*.md is therefore a build artefact. Add it to
.gitignore; the loop warns when you have not. It used to be hand-maintained
per consumer, which is precisely the failure New-AgenticComposedPrompt
exists to prevent: one consumer ended up with two full copies that had drifted
271 lines apart. One such copy had silently lost the executor’s
incident narrative, its timeout: 600000 guidance and the - [b] commit step.
Point at your overlay with a top-level block — engine-agnostic, because the prompt is:
"promptOverlays": {
"plannerPromptOverlayFile": "scripts/agentic-loop/prompts/planner-overlay.md",
"executorPromptOverlayFile": "scripts/agentic-loop/prompts/executor-overlay.md"
}
The older engines.<engine>.<role>PromptOverlayFile still works and is now
found whichever engine you run — it is read out of any engine block, not only
the selected one. Pinning that lookup to engines.claude is what left the
opencode side with no composed prompt at all and made the hand copy look
necessary. The legacy full-override engines.<engine>.<role>PromptFile also
still works: it replaces the shared prompt entirely and is mirrored into
.opencode/agents/ verbatim. This module reads it from the selected engine’s
block; the Bash twin reads engines.claude.* whatever the engine. Migrate it to
an overlay — the override shape is what makes a consumer copy the whole role
prompt in the first place.
With no prompt configuration at all, opencode gets the shared role prompt alone
and claude gets no role prompt. There is no configuration under which
opencode is left with nothing.
opencode v2#
The opencode engine requires opencode v2. Before every invocation the loop
reads opencode --version (opencode v2.0.18 on v2, a bare 1.18.33 on v1).
Anything below 2, including an unreadable answer, is a FATAL that names the
version found and how to install v2. v1 is refused rather than run, because it
takes neither of the flags below.
Install v2 from its own channels:
Host |
Command |
|---|---|
Linux, macOS, WSL |
|
Windows, Git Bash |
the same installer with |
Anywhere with Node |
|
GitHub’s opencode releases and the package managers that follow them, such as
scoop, still carry 1.x. A scoop-installed opencode is v1: uninstall it, or keep
it behind v2 on PATH.
The loop passes two flags that v1 did not need. Each one fixes something that was observed on 2.0.18:
--standalone, for both roles. Without it, a v2runattaches to the per-user background service, and the session runs there. The loop’s timeout kills the client, not the service, so a timed-out agent would go on editing the tree. A standalone server is a child of the client and dies with the process tree (Invoke-AgentProcesskills the tree, and the Bash twin kills withtimeout --kill-after).--auto, for the executor only. A headless v2runanswers every permission that resolves toaskby itself. It printspermission requested: external_directory (…); auto-rejecting, fails the tool call and exits 1.--autoapproves every rule that is not an explicitdeny. This matches the claude engine, where the executor runsbypassPermissionsand the planner is restricted. Under v2’s default policy the planner can still read the tree and editBACKLOG.md. What gets rejected is access outside the project and reads of.envfiles.
What carries over from v1 unchanged was verified against 2.0.18:
The prompt still goes in on stdin.
--agent <role>still resolves.opencode/agents/<role>.md, and the generated file needs no frontmatter.Model IDs keep the
provider/modelform. v2 adds an optional#variantsuffix.
Credentials do not carry over. A v2 install begins with No authenticated integrations, even when v1’s auth.json sits beside it. Run opencode auth login again under v2, then opencode models, before the first loop. The loop
reports a rejected model ID as Model unavailable: <id>.
For claude, engines.claude.plannerFallbackModel maps to
--fallback-model so an overloaded planner model falls back automatically (the
config template: claude-opus-5, falling back to claude-fable-5).
Model resolution precedence: $env:AGENTIC_PLANNER_MODEL /
$env:AGENTIC_EXECUTOR_MODEL > engines.<engine>.plannerModel/executorModel
legacy
models.planner/executor.
Agent invocations retry with linear backoff (up to agentRetries retries, the
Nth after N × agentRetryDelaySeconds) and honor per-role timeouts
(plannerTimeoutSeconds / executorTimeoutSeconds, falling back to
timeoutSeconds; 0 = no timeout).
Prerequisites#
OpenCode v2 CLI (install) and/or Claude Code CLI installed and authenticated (
opencode auth loginunder v2; runclaudeonce interactively)PowerShell 7+ (cross-platform)
A
BACKLOG.mdfile in the repository root (task format:- [ ] Titlefor actionable tasks,- [b] Titlefor blocked/parked ones the executor must skip)jqon Linux (for config parsing in the Bash equivalent,linux/scripts/lib/agentic-loop.sh, whose engine halfagentic-engines.shmirrors this module’s engine support)
Installation#
Place the module in your repository’s module path, then import it:
# If using ANTfrastructure as a submodule:
$modulePath = Resolve-Path 'third_party/ANTfrastructure/windows/scripts/modules/WindowsAgenticLoop.Common.psm1'
Import-Module $modulePath -Force
Do not vendor a copy into the consumer repo — resolve it ANTfrastructure-first
(the template wrapper tries third_party/ANTfrastructure before a vendored
modules/ fallback); vendored duplicates are exactly the drift the 2026-08-02
dedup pass removed.
Quick Start#
Create a Invoke-AgenticLoop.ps1 script in your project. The copy-and-edit
version, with flags and a finally around the loop, is
shared/agentic-loop/templates/Invoke-AgenticLoop.ps1; this is its core:
# scripts/agentic-loop/Invoke-AgenticLoop.ps1
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '../..')).Path
$configPath = Join-Path $PSScriptRoot 'AgenticLoop.config.json'
$config = Get-Content $configPath -Raw | ConvertFrom-Json
Import-Module (Join-Path $repoRoot 'third_party/ANTfrastructure/windows/scripts/modules/WindowsAgenticLoop.Common.psm1') -Force
Initialize-AgenticLoop -ConfigPath $configPath -RepoRoot $repoRoot
Invoke-AgenticLoop -Config $config -RepoRoot $repoRoot
Complete-AgenticLoop
Nothing else is needed: build configs are selected from the config’s
platform-appropriate buildMatrix (legacy buildConfigurations fallback)
and the planner / refactor-planner / executor task prompts default to the
shared prompt files (see Default Task Prompts).
Pass -BuildConfigs / -PlannerPrompt / -ExecutorPrompt /
-RefactorPlannerPrompt only to override.
Module API#
Initialization & Lifecycle#
Function |
Purpose |
|---|---|
|
Reset the run state and open a timestamped log under |
|
Write final summary and exit with the accumulated exit code. |
Logging#
Function |
Purpose |
|---|---|
|
Timestamped log to file + console. |
|
Decorated section header. |
|
Returns the current log file path. |
Platform Detection#
Function |
Purpose |
|---|---|
|
Returns |
|
|
Agent Invocation#
Function |
Purpose |
|---|---|
|
Resolve engine + models + prompt files + timeouts into a flat hashtable; writes |
|
Engine dispatcher with retry + linear backoff. Returns |
|
Passes message via stdin to opencode v2’s |
|
The major version in an |
|
The opencode v2 |
|
Headless |
|
Low-level process runner (stdin prompt, streamed stdout/stderr, timeout). |
|
Dispatch the fixer role with the tail of the loop log after a build failure. |
|
StrictMode-safe config lookup (hashtable or PSCustomObject). |
|
The key names of a config node (hashtable or PSCustomObject); empty array for |
|
The platform’s |
|
Per-role timeout resolution. |
Role-Prompt Composition#
What Role prompts: one composition, both engines
describes. Resolve-AgenticEngine calls these for both roles on every start.
Function |
Purpose |
|---|---|
|
Path of the shared role prompt under |
|
Overlay path: |
|
Picks overlay, full override or neither; returns the file for |
|
Shared role prompt + overlay into one temp file; returns its path. With |
|
|
|
Writes that file with its DO-NOT-EDIT header, only when the content changed. |
Default Task Prompts#
The per-phase TASK prompts (the message piped to each agent invocation —
not the role prompts, which are composed from
shared/agentic-loop/system-prompts/ and the project overlay) are
single-sourced as Markdown files in this repository:
shared/agentic-loop/prompts/planner.md
shared/agentic-loop/prompts/refactor-planner.md
shared/agentic-loop/prompts/executor.md
Function |
Purpose |
|---|---|
|
Read the shared default prompt for a role (throws if the file is missing). |
|
That file’s resolved path (same throw). |
Invoke-AgenticLoop’s -PlannerPrompt, -RefactorPlannerPrompt, and
-ExecutorPrompt parameters are optional and default to these files, so
project wrappers do not need to hard-code prompt text. The Bash library’s
default_planner_prompt / default_refactor_planner_prompt /
default_executor_prompt (in linux/scripts/lib/agentic-loop.sh) read the
same files, keeping both platforms in lockstep — edit the prompt file once
and both loops pick it up.
Utility#
Function |
Purpose |
|---|---|
|
Count actionable |
|
Count blocked |
|
Delete completed |
|
Detect a Claude usage/session-limit failure and return seconds to sleep until the stated reset (0 = not a limit failure). |
|
|
Build / Test / Quality Wrappers#
Function |
Purpose |
|---|---|
|
Execute a build command, log output, return |
|
Execute a test command, log output, return |
|
Execute a quality gate, log output. Its result never fails the loop. |
All three run the command string in a child pwsh -NoProfile -Command, so it
is PowerShell syntax and cannot change the loop’s own session.
Build Matrix & Sanitizer-Aware Testing#
Function |
Purpose |
|---|---|
|
Normalize a string or JSON object to a hashtable with |
|
Return a hashtable of env vars for the given sanitizer ( |
|
Set sanitizer env vars, run tests, restore env. |
See agentic-loop-build-matrix.md for the
full build matrix documentation.
High-Level Loop#
Function |
Purpose |
|---|---|
|
Full planner/executor loop with build matrix cycling, sanitizer-aware tests, full matrix sweeps, and quality gates. |
Invoke-AgenticLoop Parameters#
Parameter |
Type |
Description |
|---|---|---|
|
|
Configuration object ( |
|
|
Engine override ( |
|
|
Optional prompt message for the planner agent; defaults to the shared |
|
|
Optional prompt for refactor-focus iterations; defaults to the shared |
|
|
Optional prompt message for the executor agent; defaults to the shared |
|
|
Build matrix entries (string[] or object[] with name, sanitizer, testCommand, buildDir, buildType); optional — defaults to the platform’s |
|
|
|
|
|
Repository root directory |
|
|
Override max iterations (-1 = use config, 0 = unlimited) |
|
|
Skip builds |
|
|
Skip tests |
|
|
Skip quality gates |
|
|
Run planner once and exit |
|
|
Drain the queue and exit |
Config JSON Keys#
The config is read from AgenticLoop.config.json. Key sections:
Key |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Agent CLI backend: |
|
|
— |
Model ID for the planner (per engine) |
|
|
— |
Model ID for the executor (per engine) |
|
|
— |
|
|
|
— |
Repo-relative project overlay composed onto the shared planner role prompt (preferred shape) |
|
|
— |
The same for executor/fixer |
|
|
— |
Legacy full override (repo-relative) replacing the shared planner role prompt; read from the selected engine’s block |
|
|
— |
The same for executor/fixer |
|
|
— |
Space-separated |
|
|
|
Executor permission mode ( |
|
|
— |
Extra CLI args appended to every claude invocation |
|
|
|
Stream live progress (tool calls, per-turn text, final cost) via |
|
|
— |
Legacy fallback model ID for the planner agent. No model at all is FATAL |
|
|
— |
Legacy fallback model ID for the executor agent |
|
|
3 |
Build after every N completed tasks |
|
|
5 |
Quality gate every M tasks |
|
|
3 |
Refactor focus every R iterations |
|
|
0 |
Full matrix sweep every N iterations (0 = disabled) |
|
|
0 |
Max loop iterations (0 = unlimited) |
|
|
3 |
Executor runs without progress before the queue drain gives up for this iteration |
|
|
0 |
Delay between loop iterations |
|
|
0 |
Generic agent invocation timeout (0 = none) |
|
|
0 |
Planner timeout override |
|
|
0 |
Executor/fixer timeout override |
|
|
2 |
Retries per agent invocation (linear backoff) |
|
|
20 |
Base backoff delay between agent retries |
|
|
|
Dispatch the fixer agent after a failed build, then rebuild once |
|
|
3 |
Stop the loop after N consecutive failed build phases |
|
|
|
When an agent fails because the Claude usage/session limit was hit, sleep until the reset time stated in the message (+2 min) and retry without burning a retry attempt (capped at 10 waits per invocation) |
|
|
— |
Windows build matrix entries (objects with name, sanitizer, buildDir, buildType, testCommand) |
|
|
— |
Linux build matrix entries |
|
|
|
Repo-relative Windows build script, called with |
|
|
|
Repo-relative Linux build script, called with |
|
|
— |
Fallback test command for Windows |
|
|
— |
Fallback test command for Linux |
|
|
— |
Quality command for Windows |
|
|
— |
Quality command for Linux |
|
|
|
Auto-commit after each completed task |
|
|
|
Prefix for auto-commit messages |
|
|
|
Skip the planner phase while |
|
|
|
Prune completed ( |
|
|
|
Repo-relative log directory, read by |
The config template also carries intervals.testAfterBuild and
logging.logToConsole; nothing reads either.
Usage Examples#
Basic loop (with config file)#
Import-Module WindowsAgenticLoop.Common -Force
Initialize-AgenticLoop -ConfigPath 'AgenticLoop.config.json'
# ... configure and call Invoke-AgenticLoop ...
Complete-AgenticLoop
Dry run to test configuration#
Initialize-AgenticLoop -ConfigPath 'AgenticLoop.config.json' -DryRun
Invoke-AgenticLoop -Config $cfg -RepoRoot $repoRoot
Planner only (add tasks without executing)#
Invoke-AgenticLoop -Config $cfg -RepoRoot $repoRoot -PlannerOnly
Executor only (drain existing queue)#
Invoke-AgenticLoop -Config $cfg -RepoRoot $repoRoot -ExecutorOnly