Releases: zaxbysauce/opencode-swarm
v7.3.5
v7.3.5 — Restore multi-swarm primary architects (no agents in TUI/GUI after v7.3.x)
What changed
Primary fix — multi-swarm *_architect agents are visible again
After upgrading to v7.3.x, users with multi-swarm configs (swarms: { local: …, mega: …, paid: …, modelrelay: … }) reported that OpenCode showed the plugin as loaded but no swarm architect agents appeared in the GUI/TUI — only the native build and plan agents were selectable.
The plugin was importing successfully, server() was returning 90 agents and 58 tools, and the config hook was injecting all 90 agents into opencodeConfig.agent. The injected agents were all prefixed (mega_architect, paid_architect, lowtier_architect, modelrelay_architect, local_architect, …). None of them were marked mode: "primary", so OpenCode could not surface any of them as a selectable session default.
Root cause:
- v7.0.0 marked an agent primary if
agent.name === "architect"oragent.name.endsWith("_architect"). - v7.3.x added
default_agentwith a.default("architect")schema default and switched to exact-string matching (agent.name === defaultAgent). In multi-swarm configs there is no agent literally namedarchitect— they are all prefixed — so every architect was demoted to subagent. - Previous tests for
default_agentonly used the legacy unprefixed agent set, so the regression went undetected.
Fix (no OpenCode-side changes; PR #735's Windows / plugin-loading hardening is preserved):
src/config/schema.ts—default_agentis now an optional string with no schema default, so omitted vs explicit"architect"are distinguishable. Empty / whitespace-only values normalize toundefined. Arbitrary strings parse without invalidating the entire plugin config; semantic validation now happens at agent-generation time.src/agents/index.ts— new exported helperresolvePrimaryAgentNames(agentNames, defaultAgent)returns the set of generated agents that should be primary plus areasontag and an optionalwarning. Resolution rules:- Omitted ⇒ every architect-role agent (canonical base role
=== "architect") is primary. Restores v7.0.0 multi-swarm behavior. - Exact generated-name match (e.g.
"local_architect") ⇒ only that agent. - Base role in
ALL_AGENT_NAMES(e.g."coder") ⇒ every generated agent whose canonical base role matches. - Unknown / unmatched ⇒ warns once, falls back to architect-role primaries; if no architect role exists, falls back to the first generated agent. Never returns zero primaries when at least one agent exists.
"not_an_architect"is not treated as a base-role request even thoughstripKnownSwarmPrefix()returns"architect"for it — base-role matching only fires when the literal user value is itself one ofALL_AGENT_NAMES.
- Omitted ⇒ every architect-role agent (canonical base role
src/agents/index.ts:getAgentConfigs— calls the resolver once aftercreateAgents(config), marksmode: "primary"for agents in the resolver's set (withpermission.task: "allow"andmodeldeleted as before), andmode: "subagent"otherwise. Tool-filter behavior, tool snapshots, council validation, prompt/model/variant logic, and existing swarm-prefix behavior are unchanged.- Diagnostic invariant —
getAgentConfigsnow emits a deferred warning if a non-empty generated agent set produces zero primaries (defense in depth against a future regression in the resolver). The check is non-blocking.
Tests
tests/unit/config/default-agent-config.test.ts— fully replaced. Removes the now-incorrect assertions that the schema rejects arbitrary strings and defaultsdefault_agentto"architect". Adds direct unit tests forresolvePrimaryAgentNames(every resolution branch, plus thenot_an_architectmatching guard) and end-to-endgetAgentConfigstests against real multi-swarm configs, not legacy unprefixed agents. Covers:- omitted
default_agentwith prefixed-only swarms ⇒ every*_architectprimary - omitted
default_agentwith adefaultswarm + extras ⇒architectAND every*_architectprimary - exact
default_agent: "local_architect"⇒ onlylocal_architectprimary - base
default_agent: "architect"⇒ all generated architect-role agents primary - exact
default_agent: "local_coder"⇒ onlylocal_coderprimary - base
default_agent: "coder"⇒ every generated coder-role agent primary - invalid
default_agent⇒ falls back to architect-role primaries - all architects disabled + invalid
default_agent⇒ falls back to one primary, never zero - schema accepts omitted/base-role/prefixed/arbitrary strings, normalizes whitespace
- omitted
tests/unit/config/schema.test.ts— the "empty object applies defaults" test no longer expectsdefault_agent: "architect"in the parsed output (the schema default was the proximate cause of the bug).tests/integration/config-hook-multi-swarm.test.ts(new) — boots the plugin viadefault.server(ctx)against a temp project with a multi-swarm config, callshooks.config({}), and asserts thatopencodeConfig.agentcontains prefixed primary architects. This is the exact path OpenCode exercises in production.
Why
AGENTS.md invariant 11 (tool registration + agent-map coherence) now requires that any change to primary/subagent selection or default_agent test both legacy unprefixed and multi-swarm prefixed agent names. The v7.3.x test suite passed because it only used legacy single-swarm fixtures; the regression slipped past CI on every push between v7.3.0 and v7.3.4.
Migration steps
No user action required. Existing configs without default_agent now resolve every *_architect to primary, restoring the v7.0.0-compatible multi-swarm experience. Configs that explicitly set default_agent to a base role or exact generated name continue to work and now also accept arbitrary strings without invalidating the entire config (semantic validation issues a warning and falls back to architect-role primaries instead).
If you previously upgraded to v7.3.0–v7.3.4 and the plugin loaded but showed no swarm architect agents in the TUI/GUI: upgrade to v7.3.5 and run bunx opencode-swarm update to refresh OpenCode's plugin cache, then restart OpenCode.
Breaking changes
None. default_agent accepts a strictly larger set of values than before; legacy explicit values ("architect", "coder", "reviewer", etc.) keep working unchanged.
Known caveats
- An invalid
default_agentno longer causes the merged plugin config to fail Zod validation and trigger theloadPluginConfigsafe-defaults fallback. Instead the resolver warns once and falls back to architect-role primaries. This is intentional — losing an entire user config because of a typo indefault_agentwas the wrong failure mode.
Invariant audit
- 1 (plugin init): not touched
- 2 (runtime portability): not touched
- 3 (subprocesses): not touched
- 4 (.swarm containment): not touched
- 5 (plan durability): not touched
- 6 (test_runner safety): not touched
- 7 (test writing): touched — new tests use
bun:test, realswarmsfixtures (not legacy unprefixed agents), temp dirs viaos.tmpdir()+realpathSync.tests/unit/config/default-agent-config.test.tscallsmock.module('node:fs/promises', ...)to stub the agent-tool-snapshot writer — this matches the existing convention intests/unit/tools/co-change-analyzer.adversarial.test.tsand is covered by the per-file CI isolation loop. Evidence:bun --smol test tests/unit/config/default-agent-config.test.ts tests/unit/config/schema.test.ts tests/integration/config-hook-multi-swarm.test.tsall green; fulltests/unit/configgroup: 1051 pass / 30 fail (all 30 pre-existing onbe21cf0baseline; my changes net −1 failure). - 8 (session state): not touched
- 9 (guardrails/retry): not touched
- 10 (chat/system msg): not touched
- 11 (tool registration): touched — primary-vs-subagent selection rewritten via
resolvePrimaryAgentNames. Multi-swarm coverage added (see test list above) andAGENTS.mdinvariant 11 amended to require both legacy unprefixed and multi-swarm prefixed test fixtures. - 12 (release/cache): not touched —
package.json#version,CHANGELOG.md, and.release-please-manifest.jsonwere NOT manually edited. release-please owns those.
v7.3.4
v7.3.4 — Fix Windows / cross-platform plugin-load hang + repository engineering contract
What changed
Primary runtime fix — ensureSwarmGitExcluded no longer hangs plugin init (cross-platform)
The v7.3.3 commit 17fc49f added ensureSwarmGitExcluded to auto-protect .swarm/ from Git pollution before any runtime write. The function correctly handled worktrees, submodules, and tracked-file detection, but it shipped the call on the plugin-init critical path with three latent defects:
- The call site in
src/index.tsawaitedensureSwarmGitExcluded(...)with no outerwithTimeout(compare the adjacentloadSnapshotcall, which is wrapped inwithTimeout(5_000)). - Each of the four sequential
bunSpawn(['git', ...])invocations insideensureSwarmGitExcludedwas issued without a per-calltimeout. - None of those spawns set
stdin: 'ignore'. Bun on Windows can leave the child waiting for stdin EOF that never arrives.
On any host where one of the four git children fails to exit promptly — Windows 11 antivirus interception, credential helper prompts, NFS-stalled .git, sandboxed Desktop / GUI exec contexts, code-signing handshakes — the awaited Promise.all never resolves. OpenCode's plugin host silently drops a plugin whose entry never resolves (the same failure mode as issue #704), so users see "no agents in TUI / GUI" with no error message at all. The user-reported reproduction was Windows 11; the underlying defect is platform-agnostic.
Fix (defense in depth, no process.platform branches):
src/index.ts— wrap the call inwithTimeout(ensureSwarmGitExcluded(...), 3_000, ...)and treat timeout as non-fatal via.catch(...)+ the existingloghelper. Mirrors theloadSnapshotpattern.src/utils/gitignore-warning.ts— everybunSpawn(['git', ...])invocation now passes{ timeout: 1_500, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' }, with the spawn-and-await wrapped intry { … } finally { proc.kill() }so the child is guaranteed to be killed even on a runtime that ignorestimeout.src/hooks/diff-scope.ts— same hardening at bothbunSpawn(['git', 'diff', ...])sites ingetChangedFiles. This is the same defect class but not on the plugin-init path; it can hang QA-review hooks under the same host conditions.- New exports:
ENSURE_SWARM_GIT_EXCLUDED_OUTER_TIMEOUT_MS = 3_000andENSURE_SWARM_GIT_EXCLUDED_PER_CALL_TIMEOUT_MS = 1_500. The constants are reused bydiff-scope.tsso the per-call budget is consistent. - New regression test files using a file-scoped
_internalsdependency-injection seam (notmock.module, which leaks across files in Bun's shared test-runner process):tests/gitignore-warning-bounded.test.ts(3 tests: constants exported, every spawn site receives the new options, every spawn site invokesproc.kill()infinally).tests/diff-scope-bounded.test.ts(1 test: same contract forgetChangedFiles).
Repository engineering contract — AGENTS.md, engineering invariants, conventions skill, tightened commit-pr
The v7.3.3 regression belongs to a broader pattern already visible in repo history: #704 (v7.0.3, repo-graph Desktop hang), #675 (v6.86.8 / v6.86.9, plugin export shape and Node-ESM compatibility), and now #732 (v7.3.3, Git hygiene on the init path). The common defect is plugin registration awaiting unbounded environmental work. This release adds first-class repository documentation and skill hardening so future Claude Code and OpenCode agents are forced to catch this class of bug.
AGENTS.md(new, root) — concise operational engineering contract. 12 non-negotiable invariants (plugin init, runtime portability, subprocesses,.swarm/containment, plan durability,test_runnersafety, test writing, session/global state, guardrails/retry, chat/system-message hooks, tool registration, release/cache hygiene). Required reading order, prime directive, invariant-audit-required-in-PRs section.docs/engineering-invariants.md(new) — long-form rationale and historical failure map (v6.48.0, v6.80.2, v6.82.2, v6.85.1, v6.86.8, v6.86.9, v6.86.14, v7.0.1, v7.0.3, v7.3.3). Anti-pattern → required pattern → verification examples for the highest-risk invariants. Pasteable PR-description template..opencode/skills/engineering-conventions/SKILL.md(new) — OpenCode-side skill that points atAGENTS.mdand lists the highest-risk invariants. Loaded automatically before architecture / plugin-init / subprocess / tool-registration / plan-durability /.swarm/ runtime-portability changes..claude/skills/engineering-conventions/SKILL.md(new) — Claude-Code-side equivalent.CLAUDE.md(updated) — directs Claude to readAGENTS.mdfirst; clarifies that swarm-mode adds workflow structure, not exceptions to the engineering invariants..claude/skills/commit-pr/SKILL.md(updated) — new mandatoryStep −1Engineering Invariant Audit gate: readAGENTS.md, identify touched invariant categories, add## Invariant auditto PR body with concrete evidence per touched category. Hard-stop language: "If any touched invariant cannot be proven from source and test output, do not push." Step adds invariant-specific commands (build + repro-704 + dist import for plugin-init / runtime-portability / subprocess changes; subprocess grep; config + tools tests for tool registration). Pre-merge checklist now includes the invariant audit, the build/repro-704/dist-import line, the subprocess hardening line, and the explicit "do not use broadtest_runnerfor repo validation" line..opencode/skills/writing-tests/SKILL.mdand.claude/skills/writing-tests/SKILL.md(updated) — high-visibility note that the OpenCodetest_runneris for targeted agent validation only;MAX_SAFE_TEST_FILES = 50; broad scopes can stall or kill OpenCode;allow_full_suiteis for opt-in CI mirrors. For repo validation, use the shell commands in the skill.TESTING.md(updated) — points atAGENTS.md; repeats thetest_runnerwarning.contributing.md(updated) —AGENTS.mdadded to the authoritative reading flow; PR checklist gains the invariant-audit,test_runner-broad-scope, and startup-validation lines.
Why
- The Windows / plugin-load symptom was reported by a user who had updated to v7.3.3 and was greeted by an OpenCode session with no agents available in either TUI or GUI. The branch name (
claude/fix-plugin-loading-windows-ubPU2) and prior release history (#704) made the failure-mode hypothesis straightforward; the fix had to be platform-agnostic because the same defect class is reachable on macOS Desktop sandboxes and Linux Snap / Flatpak / NFS hosts. - The historical failure map shows the same engineering mistake recurring across multiple unrelated subsystems. Documenting the invariants and forcing an audit gate in
commit-pris the lowest-cost intervention that catches future instances at PR time, before they ship.
Migration steps
No user action required. The fix is transparent. On a healthy host, every git call still completes in <50 ms; the timeouts only fire on pathological hosts where the previous code would have hung indefinitely.
If you previously upgraded to v7.3.3 and the plugin failed to load on Windows 11 (or any host with an antivirus / sandbox / network home that intercepts subprocess execution): upgrade to v7.3.4 and run bunx opencode-swarm update to clear OpenCode's plugin cache (covers all three known cache layouts on Windows / macOS / Linux), then restart OpenCode.
Breaking changes
None.
Known caveats
- If the outer 3 s budget fires on a host with extremely slow git, that session does not get the
.swarm/exclude write or the tracked-file remediation warning. The plugin still loads and works. To re-run the git-hygiene check, restart OpenCode (the once-per-process flag resets on process startup). - The
_internalsDI seam exported fromsrc/utils/gitignore-warning.tsandsrc/hooks/diff-scope.tsis test-only. Production code must continue to call through it, but external callers should not import_internals— the underscore prefix marks it as a private surface.
v7.3.3
v7.3.3 — Git Hygiene: Auto-protect .swarm/ before writes
What changed
Fixed: .swarm/ runtime-artifact Git pollution
Users reported "weird uncommitted changes" with paths like git:<sha>:.swarm/dark-matter.md, indicating runtime files were tracked in Git. Every plugin startup writes to .swarm/, and tracked files bypass .gitignore, causing permanent diffs.
Changes
-
ensureSwarmGitExcluded(): New async function that auto-protects.swarm/from Git pollution before any write:- Uses
git rev-parse --show-topleveland--git-path(handles worktrees and submodules where.gitis a file, not a directory) - Checks if
.swarm/is already ignored viagit check-ignore - Appends
.swarm/to.git/info/exclude(local-only rules) if not already covered - Detects tracked
.swarm/files viagit ls-filesand emits unsuppressed remediation warning - Idempotent — safe to call multiple times;
.swarm/appears only once in exclude
- Uses
-
Protection timing:
ensureSwarmGitExcluded()now runs beforeinitTelemetry(),writeSwarmConfigExampleIfNew(), andwriteProjectConfigIfNew()to prevent any write from creating tracked files -
validateDiffScope()filter: Runtime.swarm/paths are now filtered during diff scope validation to prevent tracked runtime files from triggering false scope-violation warnings in QA review -
Unsuppressed warnings: Hygiene warnings (tracked-file remediation) are never suppressed by
quietmode; they must be visible to users -
Backward compat: Original
warnIfSwarmNotGitignored()remains exported for compatibility
Why
Tracked .swarm/ files bypass .gitignore rules entirely — Git will include them in every status and diff. The root cause was:
- No protection before the first
.swarm/write in a non-.gitignore'd repo - Lack of detection for already-tracked
.swarm/files - Advisory-only warnings that ran after damage was done
Now the protection is proactive (before any write) and uses git CLI to safely handle all repository layouts (monorepos, worktrees, submodules).
Migration steps
No migration required. The protection runs automatically on every plugin startup. If a repo has already-tracked .swarm/ files:
- A remediation warning will appear with instructions to run:
git rm -r --cached .swarm echo ".swarm/" >> .gitignore git commit -m "Stop tracking opencode-swarm runtime state"
Breaking changes
None.
Known caveats
.swarm/protection relies on.git/info/exclude(local-only rules managed per-repo) and git CLI availability. Repos with disabled git or missing.git/directory will be skipped without error.- If a repo has very old tracked
.swarm/files, users will see the remediation warning on every startup until they rungit rm -r --cached .swarm.
v7.3.2
v7.3.1
v7.3.0
v7.2.0
v7.2.0 Release Notes
What Changed
Auto-create .opencode/opencode-swarm.json on plugin init (#657)
When opencode-swarm loads in a project directory that has no local
.opencode/opencode-swarm.json, the plugin now creates one automatically.
The file is an empty JSON object ({}) that deep-merges as a no-op against
any global config. A console.warn message points users to the global config
and .swarm/config.example.json for customization guidance.
Why: First-time users had no discoverable per-project customization file
and no clear indication that one was expected. This change makes the project
config surface on first use without requiring bunx opencode-swarm install.
Design:
- Written with
{ flag: 'wx' }(exclusive create) — atomic and safe under
concurrent plugin loads;EEXISTis silently swallowed so an existing file
is never overwritten. - An empty
{}JSON object deep-merges as a mathematical no-op against any
global config (~/.config/opencode/opencode-swarm.jsonon Linux/macOS,
%APPDATA%\opencode\opencode-swarm.jsonon Windows). Users who rely
entirely on their global config will see zero change in resolved settings. - All filesystem errors (permissions, disk full, read-only mount) are caught
and swallowed — the plugin continues with defaults. Creation is non-fatal. - The one-line
console.warnthat announces creation is gated by
config.quiet, consistent with all other startup messages.
Also fixed: writeSwarmConfigExampleIfNew silently failed on brand-new
projects because .swarm/ did not yet exist when writeFileSync ran. Added
the missing mkdirSync guard so .swarm/config.example.json is now reliably
written on first use.
Files Changed
src/config/project-init.ts— new module exportingwriteProjectConfigIfNewsrc/index.ts— import and callwriteProjectConfigIfNew; fix.swarm/mkdirSync inwriteSwarmConfigExampleIfNewtests/unit/config/project-init.test.ts— 9 bun:test cases covering creation, idempotency, no-overwrite, JSON validity, quiet flag, and non-fatal error paths
Migration Steps
None. The auto-created file is an empty JSON object and has no effect on existing
global-config-only setups. Delete or ignore the file in any project where
you do not want per-project overrides.
Breaking Changes
None.
Known Caveats
- On read-only filesystems or containers without write access to the project
root, the file will not be created (non-fatal). The plugin continues normally. - The
console.warnannouncement is suppressed whenquiet: trueis set in
your global config.
v7.1.1
v7.1.0
v7.1.0 Release Notes
What Changed
Mandatory Reuse Scan & Duplicate Prevention Gate
Added a hard-gated reuse scan protocol that runs before any new function, utility type, or class is written. The Reviewer independently re-verifies the scan, and the Architect enforces the verdict field.
Four-layer defense-in-depth:
-
Coder REUSE SCAN PROTOCOL — The Coder must semantically search
src/utils/,src/hooks/,src/tools/,src/services/for existing implementations before writing new code. ReportsREUSE_SCANin DONE output with self-audit checkbox. -
Reviewer REUSE RE-VERIFICATION — The Reviewer independently runs 3+ semantic search queries per new export.
DUPLICATION_DETECTEDcauses immediate Tier 1 CORRECTNESS rejection. Cross-checks the Coder's scan report. OutputsREUSE_RE_VERIFICATIONin verdict format. -
Slop Detector Passive Telemetry — New
checkDuplicateUtilityheuristic detects name collisions in utility directories at write/edit time. Advisory only — provides observability without blocking the pipeline. -
Architect Enforcement — The Architect's Stage B gate mandates
REUSE_RE_VERIFICATIONfield presence in reviewer verdicts. Completion checklist includes the field with semantic tuple validation (e.g.,EXPORTS_ADDEDnon-empty →REUSE_RE_VERIFICATIONmust beVERIFIEDorDUPLICATION_DETECTED).
Files Changed
src/agents/coder.ts— REUSE SCAN PROTOCOL block, REUSE_SCAN field in DONE template, SELF-AUDIT checkboxsrc/agents/reviewer.ts— REUSE RE-VERIFICATION block, REUSE_RE_VERIFICATION in verdict/output formatsrc/hooks/slop-detector.ts— DUPLICATE_UTILITY heuristic, type union extensionsrc/agents/architect.ts— Stage B enforcement, ANTI-EXEMPTION pair, completion checklistsrc/agents/coder.test.ts— 15 tests for REUSE SCAN PROTOCOLsrc/agents/reviewer.test.ts— 14 tests for REUSE RE-VERIFICATIONsrc/hooks/slop-detector.test.ts— 3 new tests for DUPLICATE_UTILITYsrc/agents/architect.commands-list.test.ts— 3 new tests for architect enforcement
Migration Steps
None. All changes are additive — no existing behavior is modified. The new gates activate automatically.
Known Caveats
- The slop detector's
checkDuplicateUtilityuses name-only collision detection (no signature analysis). False positives are possible but the heuristic is advisory-only. - The reviewer's "implements the same behavior" criterion (step 3) requires human-level judgment — semantic similarity is assessed by the LLM, not a deterministic check.
v7.0.3
v7.0.3
What changed
OpenCode Desktop loading-screen hang — fixed (issue #704)
Plugin init blocked the event loop, freezing Desktop's loading screen indefinitely.
The root cause: repoGraphHook.init() was called directly in the plugin's
initializeOpenCodeSwarm function. JavaScript executes async function bodies
synchronously up to the first await, so the recursive readdir/statSync
walk held the Node/Bun event loop. OpenCode's plugin loader await server(...)
never resolved, and Desktop displayed a frozen loading screen forever. The TUI
and CLI tolerated the same blocking init because they don't depend on the same
async plugin-loader contract.
Three aggravating factors were also fixed:
-
Symlink cycles caused unbounded recursion.
findSourceFileshad no
visited-path set and usedstatSync(follows symlinks), so any cycle
(macOS iCloud/FileVault, Windows junctions, Linux FUSE) caused the walk to
loop forever. AseenRealPathsset usingrealpathSync/realpathnow
detects cycles and bails. -
maxFilescap was applied after the walk, not during. A large repo
would still complete a full scan before truncating results. The cap is now
enforced inside the traversal loop. -
Direct
Bun.*calls threwReferenceErrorunder Node. The compiled
bundle targets--target node. OpenCode's own plugin-loader source
($: typeof Bun === "undefined" ? undefined : Bun.$) confirms plugins run
under Node in some configurations. All 26Bun.file,Bun.write,
Bun.spawn,Bun.spawnSync, andBun.hashcall sites now go through a
portability shim that delegates to native Bun when available and falls back
tonode:fs/promises+node:child_processotherwise.
Why
Issue #704 was reported on macOS with OpenCode Desktop. The fix also covers
Linux (where FUSE symlink cycles are common) and Windows (where directory
junctions exhibit the same cycle behavior). The ReferenceError fix matters for
any OpenCode configuration where plugins run under Node rather than Bun.
Migration steps
No configuration changes required. The fix is transparent.
If you previously worked around the hang by restarting OpenCode or clearing
plugin state, those workarounds are no longer needed.
Breaking changes
None. The buildWorkspaceGraph / buildWorkspaceGraphAsync API is unchanged.
The new isRefusedWorkspaceRoot() guard rejects os.homedir(), /, /Users,
/home, /root, os.tmpdir(), and Windows drive roots (C:\, C:\Users)
as workspace roots — this is a safety guard, not a behavioral regression for
any real project.
Known caveats
- The
bun-compat.tsshim'sbunSpawnSyncuseschild_process.spawnSync
under Node, which is blocking. This is only called from paths that were
already synchronous; no new blocking is introduced. - The async walker yields every 200 directory entries. On extremely slow
network-mounted filesystems the yield interval may need tuning; a
walkBudgetMsoption (default: 30 000 ms) provides a hard time cap.