Skip to content

Promote dev to main (v0.7.21)#418

Merged
mabry1985 merged 15 commits intomainfrom
dev
Apr 20, 2026
Merged

Promote dev to main (v0.7.21)#418
mabry1985 merged 15 commits intomainfrom
dev

Conversation

@mabry1985
Copy link
Copy Markdown

@mabry1985 mabry1985 commented Apr 19, 2026

Summary

  • Promotes dev → main, reconciling 12-commit drift identified in board audit 2026-04-19
  • Includes v0.7.20 and v0.7.21 release bumps
  • All commits previously reviewed and merged to dev via individual PRs

Commits included

Test plan

  • CI passes on dev
  • No conflicts with main
  • Merge using --merge (merge commit — squash would break next promotion DAG)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Enhanced search with category/time-range filters and configurable result limits
    • Ava expanded with new operational skills (CI debugging, incident response, model downshift, issue triage) and richer tools for observation, orchestration, and actions
    • New GitHub issues endpoint and automated Issue Zero goals/actions driving Ava triage
  • Improvements

    • PR remediation can self-dispatch; disabled ceremonies now preserved for downstream handling
  • Documentation

    • Updated extension URIs and expanded executor/agent runtime and skills docs
  • Chores

    • Bumped release to v0.7.21; added protoAgent project; disabled PR audit ceremony

github-actions Bot and others added 12 commits April 16, 2026 22:31
Acknowledges main's squash commit as an ancestor to prevent phantom conflicts on the next dev→main promotion.
All 27 references to https://protolabs.ai/a2a/ext/* changed to
https://proto-labs.ai/a2a/ext/* to match the actual domain. These
URIs are opaque identifiers (not published specs today) but should
reference a domain we own.

Breaking: external agents (Quinn, protoPen) whose cards declare the
old URI will stop matching the registry until they update. Filed on
Quinn to update her card.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Both services decommissioned. Containers stopped + removed.
Only reference in protoWorkstacean was the rollcall script.

Note: homelab-iac/stacks/ai/docker-compose.yml still has a
worldmonitor network reference at line 521 + service at line 833.
Needs separate cleanup in that repo.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ls (#411)

Two changes:

1. Replace the basic `web_search` tool (5 results, hardcoded engines)
   with `searxng_search` — adapted from rabbit-hole.io's full-surface
   SearXNG integration. New capabilities:
   - Category routing: general, news, science, it
   - Time range filtering: day, week, month, year
   - Bang syntax: !wp (Wikipedia), !scholar, !gh (GitHub)
   - Infoboxes, direct answers, suggestions in response
   - Configurable max_results (default 10, was 5)

   Updated in both bus-tools.ts (@protolabsai/sdk pattern) and
   deep-agent-executor.ts (LangChain pattern).

2. Give Ava three fleet health tools she was missing:
   - get_ci_health — CI success rates across repos
   - get_pr_pipeline — open PRs, conflicts, staleness
   - get_incidents — security/ops incidents

   Ava can now answer fleet health questions directly instead of
   always delegating to Quinn.

Ava's tool count: 10 → 13. Tool rename: web_search → searxng_search
(greenfield, no backward compat alias).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
protoAgent is the new GitHub Template repo that replaces per-agent
A2A bootstrapping. Registers it as an active dev project owned by
Quinn, matching the shape of existing entries. Plane / GitHub
webhook / Discord provisioning remain TODO — those integrations
aren't configured in this deployment, so the onboard plugin
skipped them.

Co-authored-by: Josh <artificialcitizens@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Acknowledges main's squash commit as an ancestor to prevent phantom conflicts on the next dev→main promotion.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…e bug (#415)

Ava agent audit + overhaul:
- Tools: 10 → 22 (direct observation, propose_config_change, incident reporting)
- Skills: 3 → 7 (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills)
- System prompt rewritten: self-improvement instructions, escalation policy, GOAP-dispatch playbook
- DeepAgentExecutor now applies skill-level systemPromptOverride (goal_proposal, diagnose_pr_stuck)
- Fix ceremony loader bug: disabled ceremonies were filtered out, preventing hot-reload from cancelling timers
- Clean up board.pr-audit.yaml (remove spurious action field, restore schedule, keep disabled)
- Update docs: README, deep-agent runtime, agent-skills reference, self-improving loop

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-approve (#417)

Two root causes prevented PRs from being auto-merged:

1. Dispatch gap: tier_0 short-circuit in ActionDispatcherPlugin completed
   all actions immediately without dispatching to agent.skill.request.
   Every action in actions.yaml is tier_0, so the fireAndForget path
   (which publishes the skill request) was unreachable dead code.
   Fix: tier_0 now falls through when meta.fireAndForget is set.

2. Approval gap: readyToMerge requires reviewState=approved, but
   auto-approve only covered dependabot/renovate/promote:/chore(deps.
   Human PRs, release PRs (chore(release), and github-actions PRs
   all lacked approved reviews and sat indefinitely.
   Fix: added app/github-actions to authors, chore(release, chore:,
   docs( to safe title prefixes.

Additionally, PrRemediatorPlugin now self-dispatches remediation on
every world.state.updated tick — checking for readyToMerge, dirty,
failingCi, and changesRequested PRs directly from cached domain data.
This removes the dependency on GOAP dispatch reaching the plugin via
pr.remediate.* topics (which were never published in production after
Arc 1.4 removed meta.topic routing).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

Warning

Rate limit exceeded

@mabry1985 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 21 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 7 minutes and 21 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5c1e7ee4-0d07-4394-aee6-3b96fcbcda65

📥 Commits

Reviewing files that changed from the base of the PR and between 58daec5 and 5576e72.

📒 Files selected for processing (5)
  • lib/plugins/pr-remediator.ts
  • src/agent-runtime/tools/bus-tools.ts
  • src/api/board.ts
  • src/executor/extensions/langfuse-trace.ts
  • src/index.ts
📝 Walkthrough

Walkthrough

This PR updates extension URIs (protolabs.ai → proto-labs.ai), replaces web_search with an enhanced searxng_search, adds per-skill systemPromptOverride in DeepAgentExecutor, introduces PR remediator self-dispatch, preserves disabled ceremonies, expands Ava agent tools/skills and Issue Zero goals/actions, and adds GitHub-issues polling/route.

Changes

Cohort / File(s) Summary
Extension URI Updates
docs/extensions/blast-v1.md, docs/extensions/confidence-v1.md, docs/extensions/cost-v1.md, docs/extensions/effect-domain-v1.md, docs/extensions/hitl-mode-v1.md, docs/guides/extend-an-a2a-agent.md, src/executor/extensions/blast.ts, src/executor/extensions/confidence.ts, src/executor/extensions/cost.ts, src/executor/extensions/effect-domain.ts, src/executor/extensions/hitl-mode.ts, src/executor/task-tracker.ts
Replaced https://protolabs.ai/... URIs with https://proto-labs.ai/... in docs and exported constants; task-tracker reference updated.
Search Tool Replacement
src/agent-runtime/tools/bus-tools.ts, src/executor/executors/deep-agent-executor.ts
web_searchsearxng_search: expanded input schema (category, time_range, max_results), changed request URL construction and headers, increased timeout (10s→15s), and broadened response parsing (results, answers, suggestions, infoboxes).
DeepAgentExecutor prompt handling
src/executor/executors/deep-agent-executor.ts, docs/integrations/runtimes/deep-agent.md
Resolve per-skill systemPromptOverride when present (fallback to agent systemPrompt) and append cached world-state snapshot; docs updated accordingly and Ava example adjusted (model, maxTurns, skills, tools).
Ava agent & skills
workspace/agents/ava.yaml, docs/reference/agent-skills.md
Expanded Ava tools (observation/orchestration/write/feedback), added GOAP-dispatched skills (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills, issue_triage), removed chain from skill schema and added systemPromptOverride field.
Issue Zero: GitHub issues integration
src/api/github.ts, src/index.ts, workspace/actions.yaml, workspace/goals.yaml
New GET /api/github-issues route aggregating open-issue counts (per-repo and totals), registered github_issues polling domain, added Issue Zero goals and actions that trigger Ava triage dispatches and alerts based on thresholds.
PR Remediator / self-dispatch
lib/plugins/pr-remediator.ts, src/pr-remediator.test.ts
Added _selfDispatchRemediation() invoked on world.state.updated ticks to drive remediation from cached PR snapshot; broadened AUTO_APPROVE_AUTHORS/title prefixes; tests updated to exercise self-dispatch flows and HITL escalation.
Ceremony loader & tests
src/loaders/ceremonyYamlLoader.ts, src/loaders/__tests__/ceremonyYamlLoader.test.ts, src/plugins/__tests__/CeremonyPlugin.test.ts, workspace/ceremonies/board.pr-audit.yaml
Disabled ceremonies preserved (no early-return); tests adjusted; board.pr-audit disabled (enabled: false).
Action dispatch behavior
src/plugins/action-dispatcher-plugin.ts
Tier-0 actions now only auto-complete when tier === "tier_0" AND !action.meta.fireAndForget; fireAndForget actions proceed to normal dispatch/publishing.
Tooling / constants updates
src/executor/extensions/*.ts, src/executor/task-tracker.ts
Exported extension URI constants updated to new domain; task-tracker emits world-state delta with updated URI reference.
Tests & test adjustments
src/pr-remediator.test.ts, src/loaders/__tests__/ceremonyYamlLoader.test.ts, src/plugins/__tests__/CeremonyPlugin.test.ts
Multiple tests updated to reflect self-dispatch, preserved disabled ceremonies, and new control-flow expectations.
Version bumps & project config
package.json, dashboard/package.json, workspace/projects.yaml
Package versions updated to 0.7.21; added protoagent project entry (metadata, agents, discord placeholders).
README & docs
README.md, docs/explanation/self-improving-loop.md, other docs
README updated to show DeepAgentExecutor and revised executor targets; self-improving loop docs expanded describing chronic-failure → goal proposals, Ava-driven config diffs (propose_config_change), and GOAP-dispatched operational skills.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 A hop, a sniff, a searxng query bright,

Ava learns prompts by skill, then sees the night,
Self-dispatching fixes, PRs set right,
Disabled ceremonies sleep till called to light,
Config diffs proposed with carroted delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Promote dev to main (v0.7.21)' directly and clearly describes the main objective of the PR: promoting the dev branch to main with release version v0.7.21.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plugins/__tests__/CeremonyPlugin.test.ts (1)

61-79: ⚠️ Potential issue | 🟡 Minor

Test name overstates behavior that isn’t asserted.

At Line 61 the test says disabled ceremonies are “not scheduled,” but assertions at Line 77 and Line 78 only verify load-state (enabled: false). Either assert unscheduled behavior explicitly or rename the test to match what it validates.

✏️ Minimal fix (rename to match current assertions)
-  test("CeremonyPlugin loader: loads disabled ceremonies but does not schedule them", () => {
+  test("CeremonyPlugin loader: loads disabled ceremonies with enabled: false", () => {
@@
-    // Disabled ceremonies are loaded (so hot-reload can cancel timers) but not scheduled
+    // Disabled ceremonies are loaded (so hot-reload can cancel timers)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/__tests__/CeremonyPlugin.test.ts` around lines 61 - 79, The
test’s title ("CeremonyPlugin loader: loads disabled ceremonies but does not
schedule them") overstates behavior; update the test name to reflect asserted
behavior or add an assertion that checks scheduling. Either rename the test
string to "CeremonyPlugin loader: loads disabled ceremonies" (modify the
test(...) invocation that wraps writeCeremony / plugin.install /
plugin.getCeremonies) or add an explicit assertion after
plugin.install/getCeremonies to verify the ceremony is not scheduled (e.g.,
check the scheduler/timer registry or plugin API that would contain scheduled
jobs) so the title matches the actual checks.
🧹 Nitpick comments (1)
workspace/ceremonies/board.pr-audit.yaml (1)

8-8: Disabling this ceremony will stop scheduled PR audits.
On Line 8, enabled: false correctly unschedules cron execution; just confirm your replacement trigger path is active before merge (to avoid silent audit gaps).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspace/ceremonies/board.pr-audit.yaml` at line 8, You disabled scheduled
PR audits by setting the ceremony flag `enabled: false`; before merging, verify
and activate the replacement trigger path (the new scheduled or event-driven job
that will run PR audits) so audits don't stop silently — confirm the alternative
trigger is deployed, correctly referenced by the PR-audit workflow, and tested
end-to-end to ensure audit runs continue after this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/explanation/self-improving-loop.md`:
- Around line 188-193: The documentation incorrectly states that every
propose_config_change requires an evidence block; update the paragraph so it
clarifies that only tuning-agent proposals require an evidence block returned by
get_confidence_summary (with sampleCount >= 50) or get_cost_summary, while goal
and action proposals do not require evidence. Reference the
propose_config_change tool and explicitly state that evidence is mandatory only
for "tuning-agent" proposals (e.g., agent tuning like "Quinn's pr_review..."),
and remove or reword the blanket sentence that makes evidence sound mandatory
for all config changes.

In `@docs/integrations/runtimes/deep-agent.md`:
- Around line 49-52: The docs list the deprecated tool name "web_search" in the
sample YAML and tools table; update every occurrence of "web_search" to the
runtime's current tool name "searxng_search" (e.g., in the block containing
get_cost_summary, get_confidence_summary, web_search and the same entries at
lines ~114-116) so the example config and tool list match the runtime API and
preserve search functionality when copied.

In `@docs/reference/agent-skills.md`:
- Line 76: The tools list on the line containing get_world_state, get_projects,
get_ci_health, get_pr_pipeline, get_branch_drift, get_outcomes, get_incidents,
get_ceremonies, get_cost_summary, get_confidence_summary currently still lists
web_search; update that entry to searxng_search (and any surrounding text that
references the old tool name) so the documented tool names match the promoted
search-tool migration and avoid confusing agent authors.

In `@lib/plugins/pr-remediator.ts`:
- Around line 361-372: The AUTO_APPROVE_AUTHORS and AUTO_APPROVE_TITLE_PREFIXES
are too permissive (AUTO_APPROVE_AUTHORS, AUTO_APPROVE_TITLE_PREFIXES) and
_runAutoApprovePass() currently auto-submits approvals for any CI-passing match;
tighten this by removing or restricting loose entries like "app/github-actions",
"chore:", and "docs(" from AUTO_APPROVE_TITLE_PREFIXES (or scope them to
specific workflow identities in AUTO_APPROVE_AUTHORS), and add a guarded check
in the auto-approve flow (inside _runAutoApprovePass or a helper it calls) that
verifies changed files are limited to allowed surfaces (e.g., only docs/,
release metadata, dependency manifests, or back-merge-only paths) before
approving; ensure the new verification references the PR file list and uses the
same symbols (AUTO_APPROVE_AUTHORS, AUTO_APPROVE_TITLE_PREFIXES,
_runAutoApprovePass) so the logic only auto-approves when both author/workflow
and restricted file-scope conditions are met.

In `@src/agent-runtime/tools/bus-tools.ts`:
- Around line 401-407: The code uses new URL("/search", endpoint) which discards
any path in SEARXNG_URL; instead, create a URL object from endpoint (const base
= new URL(endpoint)), normalize and append the search path to base.pathname
(e.g. base.pathname = base.pathname.replace(/\/$/, '') + '/search'), then use
that URL (assign to url) so requests go to the configured path-prefixed host;
update references to endpoint and url accordingly.

In `@src/executor/executors/deep-agent-executor.ts`:
- Around line 273-289: The fetch response is parsed without checking HTTP
status, so non-OK SearxNG responses (429/500) will cause resp.json() to throw
and crash the agent; change the code around the fetch/resp handling in
deep-agent-executor.ts to first check resp.ok and, if false, read the response
body (e.g., await resp.text()) and surface a recoverable tool error (match the
behavior in src/agent-runtime/tools/bus-tools.ts by throwing or returning an
error message like `SearxNG returned ${resp.status}: ${body}`) instead of
calling resp.json(); keep the successful path unchanged (use resp.json() and the
existing mapping using input.max_results, cap, and the
results/answers/suggestions/infoboxes transformation).

In `@workspace/agents/ava.yaml`:
- Line 56: The YAML still references the removed tool name web_search in the Ava
prompt text; update those occurrences to the new tool name searxng_search so the
prompt and runtime match. Specifically, replace "web_search" with
"searxng_search" in the Ava system/instruction copy (the lines around where the
prompt lists tools and the line near 190) so the prompt instructs Ava to use the
registered DeepAgentExecutor tool searxng_search and aligns with BUS_TOOL_NAMES
and the registered tool list.
- Around line 114-119: The policy text erroneously requires evidence from
get_cost_summary or get_confidence_summary for every propose_config_change;
change it so only agent-tuning proposals require an evidence block (with
sampleCount >= 50 and data from get_cost_summary or get_confidence_summary),
while new-goal and new-action proposals do not require that evidence; update the
describe/validation text around propose_config_change and references to
get_cost_summary/get_confidence_summary to reflect this distinction.

---

Outside diff comments:
In `@src/plugins/__tests__/CeremonyPlugin.test.ts`:
- Around line 61-79: The test’s title ("CeremonyPlugin loader: loads disabled
ceremonies but does not schedule them") overstates behavior; update the test
name to reflect asserted behavior or add an assertion that checks scheduling.
Either rename the test string to "CeremonyPlugin loader: loads disabled
ceremonies" (modify the test(...) invocation that wraps writeCeremony /
plugin.install / plugin.getCeremonies) or add an explicit assertion after
plugin.install/getCeremonies to verify the ceremony is not scheduled (e.g.,
check the scheduler/timer registry or plugin API that would contain scheduled
jobs) so the title matches the actual checks.

---

Nitpick comments:
In `@workspace/ceremonies/board.pr-audit.yaml`:
- Line 8: You disabled scheduled PR audits by setting the ceremony flag
`enabled: false`; before merging, verify and activate the replacement trigger
path (the new scheduled or event-driven job that will run PR audits) so audits
don't stop silently — confirm the alternative trigger is deployed, correctly
referenced by the PR-audit workflow, and tested end-to-end to ensure audit runs
continue after this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e152dea7-e156-4e25-8c8c-e5b9a96a4d10

📥 Commits

Reviewing files that changed from the base of the PR and between fc4ce52 and 9dc4b79.

📒 Files selected for processing (29)
  • README.md
  • dashboard/package.json
  • docs/explanation/self-improving-loop.md
  • docs/extensions/blast-v1.md
  • docs/extensions/confidence-v1.md
  • docs/extensions/cost-v1.md
  • docs/extensions/effect-domain-v1.md
  • docs/extensions/hitl-mode-v1.md
  • docs/guides/extend-an-a2a-agent.md
  • docs/integrations/runtimes/deep-agent.md
  • docs/reference/agent-skills.md
  • lib/plugins/pr-remediator.ts
  • package.json
  • src/agent-runtime/tools/bus-tools.ts
  • src/executor/executors/deep-agent-executor.ts
  • src/executor/extensions/blast.ts
  • src/executor/extensions/confidence.ts
  • src/executor/extensions/cost.ts
  • src/executor/extensions/effect-domain.ts
  • src/executor/extensions/hitl-mode.ts
  • src/executor/task-tracker.ts
  • src/loaders/__tests__/ceremonyYamlLoader.test.ts
  • src/loaders/ceremonyYamlLoader.ts
  • src/plugins/__tests__/CeremonyPlugin.test.ts
  • src/plugins/action-dispatcher-plugin.ts
  • src/pr-remediator.test.ts
  • workspace/agents/ava.yaml
  • workspace/ceremonies/board.pr-audit.yaml
  • workspace/projects.yaml

Comment on lines +188 to +193
This lets Ava close the loop at every layer:
- **New goals** — "CI has no goal for main-branch-red; I'll propose one"
- **New actions** — "goal is violated but no action addresses it"
- **Agent tuning** — "Quinn's pr_review is too expensive at Opus; downshift to Sonnet"

All proposals require evidence from `get_cost_summary` or `get_confidence_summary` (sampleCount >= 50 for agent tuning).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don't document evidence as mandatory for every config change.

propose_config_change only requires an evidence block for tuning-agent proposals. As written, Line 193 contradicts the tool contract and makes goal/action proposals sound stricter than they are.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/explanation/self-improving-loop.md` around lines 188 - 193, The
documentation incorrectly states that every propose_config_change requires an
evidence block; update the paragraph so it clarifies that only tuning-agent
proposals require an evidence block returned by get_confidence_summary (with
sampleCount >= 50) or get_cost_summary, while goal and action proposals do not
require evidence. Reference the propose_config_change tool and explicitly state
that evidence is mandatory only for "tuning-agent" proposals (e.g., agent tuning
like "Quinn's pr_review..."), and remove or reword the blanket sentence that
makes evidence sound mandatory for all config changes.

Comment on lines +49 to +52
- get_cost_summary
- get_confidence_summary
- web_search
# Write / Act
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Replace web_search with searxng_search in the docs.

The runtime now exposes searxng_search; keeping web_search in the sample YAML and tool table documents a tool name that no longer exists. Copy-pasted configs from this page will silently lose search support.

Also applies to: 114-116

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/integrations/runtimes/deep-agent.md` around lines 49 - 52, The docs list
the deprecated tool name "web_search" in the sample YAML and tools table; update
every occurrence of "web_search" to the runtime's current tool name
"searxng_search" (e.g., in the block containing get_cost_summary,
get_confidence_summary, web_search and the same entries at lines ~114-116) so
the example config and tool list match the runtime API and preserve search
functionality when copied.

Available tools (see [DeepAgent Runtime](../integrations/runtimes/deep-agent) for the full list):

**Orchestration:** `chat_with_agent`, `delegate_task`, `publish_event`, `manage_cron`, `run_ceremony`
**Observation:** `get_world_state`, `get_projects`, `get_ci_health`, `get_pr_pipeline`, `get_branch_drift`, `get_outcomes`, `get_incidents`, `get_ceremonies`, `get_cost_summary`, `get_confidence_summary`, `web_search`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tool list appears stale: web_search should likely be searxng_search.

Line 76 still documents web_search, which conflicts with this promotion’s stated search-tool migration and can mislead agent authors.

🛠️ Suggested doc fix
-**Observation:** `get_world_state`, `get_projects`, `get_ci_health`, `get_pr_pipeline`, `get_branch_drift`, `get_outcomes`, `get_incidents`, `get_ceremonies`, `get_cost_summary`, `get_confidence_summary`, `web_search`
+**Observation:** `get_world_state`, `get_projects`, `get_ci_health`, `get_pr_pipeline`, `get_branch_drift`, `get_outcomes`, `get_incidents`, `get_ceremonies`, `get_cost_summary`, `get_confidence_summary`, `searxng_search`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**Observation:** `get_world_state`, `get_projects`, `get_ci_health`, `get_pr_pipeline`, `get_branch_drift`, `get_outcomes`, `get_incidents`, `get_ceremonies`, `get_cost_summary`, `get_confidence_summary`, `web_search`
**Observation:** `get_world_state`, `get_projects`, `get_ci_health`, `get_pr_pipeline`, `get_branch_drift`, `get_outcomes`, `get_incidents`, `get_ceremonies`, `get_cost_summary`, `get_confidence_summary`, `searxng_search`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/reference/agent-skills.md` at line 76, The tools list on the line
containing get_world_state, get_projects, get_ci_health, get_pr_pipeline,
get_branch_drift, get_outcomes, get_incidents, get_ceremonies, get_cost_summary,
get_confidence_summary currently still lists web_search; update that entry to
searxng_search (and any surrounding text that references the old tool name) so
the documented tool names match the promoted search-tool migration and avoid
confusing agent authors.

Comment on lines +361 to +372
* - dependabot / renovate / github-actions authors (automated changes)
* - "promote:" title prefix (release pipeline — content already reviewed)
* - "chore(deps" title prefix (manual dep bump)
* - "chore(deps" / "chore(release" title prefix (automated dep/version bumps)
* - "docs(" title prefix (documentation-only changes)
* - "chore:" prefix for back-merge and other maintenance PRs
*
* All three classes are structurally safe: they either contain no
* hand-written code (dep bumps) or are re-promoting content that's
* already passed review on the upstream branch.
* All classes are structurally safe: they either contain no hand-written
* code (dep bumps, releases, back-merges), are re-promoting content
* that's already passed review, or are documentation-only.
*/
const AUTO_APPROVE_AUTHORS = new Set(["dependabot[bot]", "renovate[bot]"]);
const AUTO_APPROVE_TITLE_PREFIXES = ["promote:", "chore(deps"];
const AUTO_APPROVE_AUTHORS = new Set(["dependabot[bot]", "renovate[bot]", "app/github-actions"]);
const AUTO_APPROVE_TITLE_PREFIXES = ["promote:", "chore(deps", "chore(release", "chore:", "docs("];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Narrow the new auto-approve classes.

app/github-actions, chore:, and docs( are not strong safety signals on their own. With _runAutoApprovePass() later auto-submitting APPROVED reviews for any passing-CI match, this can remove the last human gate from arbitrary code-changing PRs that happen to use those prefixes. Limit this to tightly controlled identities/workflows, or verify the changed files are actually restricted to docs/release/back-merge surfaces before approving.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/plugins/pr-remediator.ts` around lines 361 - 372, The
AUTO_APPROVE_AUTHORS and AUTO_APPROVE_TITLE_PREFIXES are too permissive
(AUTO_APPROVE_AUTHORS, AUTO_APPROVE_TITLE_PREFIXES) and _runAutoApprovePass()
currently auto-submits approvals for any CI-passing match; tighten this by
removing or restricting loose entries like "app/github-actions", "chore:", and
"docs(" from AUTO_APPROVE_TITLE_PREFIXES (or scope them to specific workflow
identities in AUTO_APPROVE_AUTHORS), and add a guarded check in the auto-approve
flow (inside _runAutoApprovePass or a helper it calls) that verifies changed
files are limited to allowed surfaces (e.g., only docs/, release metadata,
dependency manifests, or back-merge-only paths) before approving; ensure the new
verification references the PR file list and uses the same symbols
(AUTO_APPROVE_AUTHORS, AUTO_APPROVE_TITLE_PREFIXES, _runAutoApprovePass) so the
logic only auto-approves when both author/workflow and restricted file-scope
conditions are met.

Comment on lines +395 to +423
query: z.string().describe("Search query. Supports SearXNG bang syntax (e.g. !wp, !scholar, !gh)."),
category: z.enum(["general", "news", "science", "it"]).optional().describe("SearXNG category. Defaults to general."),
time_range: z.enum(["day", "week", "month", "year"]).optional().describe("Limit results to a time range."),
max_results: z.number().optional().describe("Max results to return. Default: 10."),
},
async ({ query }) => {
const searxngUrl = process.env.SEARXNG_URL ?? "http://searxng:8080";
async ({ query, category, time_range, max_results }) => {
const endpoint = process.env.SEARXNG_URL ?? "http://searxng:8080";
try {
const params = new URLSearchParams({ q: query, format: "json", engines: "google,duckduckgo" });
const resp = await fetch(`${searxngUrl}/search?${params}`, { signal: AbortSignal.timeout(10_000) });
const url = new URL("/search", endpoint);
url.searchParams.set("q", query);
url.searchParams.set("format", "json");
if (category) url.searchParams.set("categories", category);
if (time_range) url.searchParams.set("time_range", time_range);

const resp = await fetch(url.toString(), {
headers: { Accept: "application/json" },
signal: AbortSignal.timeout(15_000),
});
if (!resp.ok) return err(`SearXNG ${resp.status}`);
const data = await resp.json() as { results?: Array<{ title: string; content?: string; url?: string }> };
const results = (data.results ?? []).slice(0, 5);
if (!results.length) return ok({ results: [], message: "No results found." });
return ok({ results: results.map(r => ({ title: r.title, snippet: r.content ?? "", url: r.url ?? "" })) });

const data = await resp.json() as {
results?: Array<{ title: string; url: string; content: string; engine: string; score?: number }>;
suggestions?: string[];
infoboxes?: Array<{ infobox: string; content: string; attributes?: Array<{ label: string; value: string }> }>;
answers?: string[];
};

const cap = max_results ?? 10;
const results = (data.results ?? []).slice(0, cap).map(r => ({
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Enforce the documented default category and validate max_results.

The schema says category defaults to general, but omitted requests never set categories, so behavior depends on server config instead of this tool contract. max_results also accepts negative or fractional values, which feed straight into slice(0, cap).

Suggested fix
-      category: z.enum(["general", "news", "science", "it"]).optional().describe("SearXNG category. Defaults to general."),
-      max_results: z.number().optional().describe("Max results to return. Default: 10."),
+      category: z.enum(["general", "news", "science", "it"]).default("general").describe("SearXNG category."),
+      max_results: z.number().int().positive().optional().describe("Max results to return. Default: 10."),
     },
-    async ({ query, category, time_range, max_results }) => {
+    async ({ query, category = "general", time_range, max_results }) => {
       const endpoint = process.env.SEARXNG_URL ?? "http://searxng:8080";
       try {
         const url = new URL("/search", endpoint);
         url.searchParams.set("q", query);
         url.searchParams.set("format", "json");
-        if (category) url.searchParams.set("categories", category);
+        url.searchParams.set("categories", category);

Comment on lines +401 to +407
const endpoint = process.env.SEARXNG_URL ?? "http://searxng:8080";
try {
const params = new URLSearchParams({ q: query, format: "json", engines: "google,duckduckgo" });
const resp = await fetch(`${searxngUrl}/search?${params}`, { signal: AbortSignal.timeout(10_000) });
const url = new URL("/search", endpoint);
url.searchParams.set("q", query);
url.searchParams.set("format", "json");
if (category) url.searchParams.set("categories", category);
if (time_range) url.searchParams.set("time_range", time_range);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve path-prefixed SEARXNG_URL values.

new URL("/search", endpoint) discards any path component in SEARXNG_URL. If this is configured as something like https://search.example.com/searxng, requests will go to /search at the origin root instead of /searxng/search.

Suggested fix
-        const url = new URL("/search", endpoint);
+        const base = endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
+        const url = new URL("search", base);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agent-runtime/tools/bus-tools.ts` around lines 401 - 407, The code uses
new URL("/search", endpoint) which discards any path in SEARXNG_URL; instead,
create a URL object from endpoint (const base = new URL(endpoint)), normalize
and append the search path to base.pathname (e.g. base.pathname =
base.pathname.replace(/\/$/, '') + '/search'), then use that URL (assign to url)
so requests go to the configured path-prefixed host; update references to
endpoint and url accordingly.

Comment on lines +273 to +289
const resp = await fetch(url.toString(), {
headers: { Accept: "application/json" },
signal: AbortSignal.timeout(15_000),
});
const data = await resp.json() as {
results?: Array<{ title: string; url: string; content: string; engine: string; score?: number }>;
answers?: string[];
suggestions?: string[];
infoboxes?: Array<{ infobox: string; content: string }>;
};
const cap = input.max_results ?? 10;
return JSON.stringify({
results: (data.results ?? []).slice(0, cap).map(r => ({ title: r.title, url: r.url, snippet: r.content, engine: r.engine })),
...(data.answers?.length ? { answers: data.answers } : {}),
...(data.suggestions?.length ? { suggestions: data.suggestions } : {}),
...(data.infoboxes?.length ? { infoboxes: data.infoboxes.map(ib => ({ title: ib.infobox, content: ib.content })) } : {}),
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle non-OK SearXNG responses before parsing JSON.

A 429/500 response currently falls straight into resp.json(), so an upstream error can throw out of the tool and fail the whole agent run. src/agent-runtime/tools/bus-tools.ts:387-435 already treats non-OK SearXNG responses as a recoverable tool failure; this copy should match it.

Suggested fix
         const resp = await fetch(url.toString(), {
           headers: { Accept: "application/json" },
           signal: AbortSignal.timeout(15_000),
         });
+        if (!resp.ok) {
+          return JSON.stringify({ results: [], error: `SearXNG ${resp.status}` });
+        }
         const data = await resp.json() as {
           results?: Array<{ title: string; url: string; content: string; engine: string; score?: number }>;
           answers?: string[];
           suggestions?: string[];
           infoboxes?: Array<{ infobox: string; content: string }>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resp = await fetch(url.toString(), {
headers: { Accept: "application/json" },
signal: AbortSignal.timeout(15_000),
});
const data = await resp.json() as {
results?: Array<{ title: string; url: string; content: string; engine: string; score?: number }>;
answers?: string[];
suggestions?: string[];
infoboxes?: Array<{ infobox: string; content: string }>;
};
const cap = input.max_results ?? 10;
return JSON.stringify({
results: (data.results ?? []).slice(0, cap).map(r => ({ title: r.title, url: r.url, snippet: r.content, engine: r.engine })),
...(data.answers?.length ? { answers: data.answers } : {}),
...(data.suggestions?.length ? { suggestions: data.suggestions } : {}),
...(data.infoboxes?.length ? { infoboxes: data.infoboxes.map(ib => ({ title: ib.infobox, content: ib.content })) } : {}),
});
const resp = await fetch(url.toString(), {
headers: { Accept: "application/json" },
signal: AbortSignal.timeout(15_000),
});
if (!resp.ok) {
return JSON.stringify({ results: [], error: `SearXNG ${resp.status}` });
}
const data = await resp.json() as {
results?: Array<{ title: string; url: string; content: string; engine: string; score?: number }>;
answers?: string[];
suggestions?: string[];
infoboxes?: Array<{ infobox: string; content: string }>;
};
const cap = input.max_results ?? 10;
return JSON.stringify({
results: (data.results ?? []).slice(0, cap).map(r => ({ title: r.title, url: r.url, snippet: r.content, engine: r.engine })),
...(data.answers?.length ? { answers: data.answers } : {}),
...(data.suggestions?.length ? { suggestions: data.suggestions } : {}),
...(data.infoboxes?.length ? { infoboxes: data.infoboxes.map(ib => ({ title: ib.infobox, content: ib.content })) } : {}),
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/executor/executors/deep-agent-executor.ts` around lines 273 - 289, The
fetch response is parsed without checking HTTP status, so non-OK SearxNG
responses (429/500) will cause resp.json() to throw and crash the agent; change
the code around the fetch/resp handling in deep-agent-executor.ts to first check
resp.ok and, if false, read the response body (e.g., await resp.text()) and
surface a recoverable tool error (match the behavior in
src/agent-runtime/tools/bus-tools.ts by throwing or returning an error message
like `SearxNG returned ${resp.status}: ${body}`) instead of calling resp.json();
keep the successful path unchanged (use resp.json() and the existing mapping
using input.max_results, cap, and the results/answers/suggestions/infoboxes
transformation).

Comment thread workspace/agents/ava.yaml
- **get_incidents** — Open security/operational incidents.
- **get_cost_summary** — Per-agent/skill cost: tokens, duration, dollars.
- **get_confidence_summary** — Per-agent/skill calibration metrics.
- **web_search** — Quick web search. For deep research, delegate to Researcher.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Rename the removed web_search tool here.

DeepAgentExecutor now registers searxng_search, and BUS_TOOL_NAMES no longer includes web_search. Keeping the old name in Lines 56 and 190 means Ava loses web search at runtime while the prompt still tells her to use it.

Suggested fix
-  - **web_search** — Quick web search. For deep research, delegate to Researcher.
+  - **searxng_search** — Quick web search. For deep research, delegate to Researcher.
...
-  - web_search
+  - searxng_search

Also applies to: 190-190

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspace/agents/ava.yaml` at line 56, The YAML still references the removed
tool name web_search in the Ava prompt text; update those occurrences to the new
tool name searxng_search so the prompt and runtime match. Specifically, replace
"web_search" with "searxng_search" in the Ava system/instruction copy (the lines
around where the prompt lists tools and the line near 190) so the prompt
instructs Ava to use the registered DeepAgentExecutor tool searxng_search and
aligns with BUS_TOOL_NAMES and the registered tool list.

Comment thread workspace/agents/ava.yaml
Comment on lines +114 to +119
**Propose improvements:** Use `propose_config_change` to propose:
- New goals (goal violated but no goal exists to catch it)
- New actions (goal violated but no action addresses it)
- Agent tuning (cost/confidence data shows miscalibration)
All proposals require evidence from `get_cost_summary` or
`get_confidence_summary`. Include sampleCount >= 50 for agent tuning.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't require cost/confidence evidence for every config proposal.

propose_config_change only requires an evidence block for agent-tuning proposals. Telling Ava that all proposals must come from get_cost_summary or get_confidence_summary will block the new-goal/new-action paths this PR is adding.

Suggested fix
-  All proposals require evidence from `get_cost_summary` or
-  `get_confidence_summary`. Include sampleCount >= 50 for agent tuning.
+  Data-driven tuning proposals should include evidence from
+  `get_cost_summary` or `get_confidence_summary`.
+  Include sampleCount >= 50 for agent tuning.
+  Goal/action proposals may instead rely on the triggering incident,
+  outcome cluster, or world-state evidence.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspace/agents/ava.yaml` around lines 114 - 119, The policy text
erroneously requires evidence from get_cost_summary or get_confidence_summary
for every propose_config_change; change it so only agent-tuning proposals
require an evidence block (with sampleCount >= 50 and data from get_cost_summary
or get_confidence_summary), while new-goal and new-action proposals do not
require that evidence; update the describe/validation text around
propose_config_change and references to get_cost_summary/get_confidence_summary
to reflect this distinction.

…ss fleet (#419)

Adds a github_issues domain that polls /repos/{repo}/issues?state=open
for all managed projects and classifies by label (critical, bug,
enhancement). Three GOAP goals enforce issue hygiene:

  - issues.zero_critical (critical severity, max: 0)
  - issues.zero_bugs (high severity, max: 0)
  - issues.total_low (medium severity, max: 5)

Each goal has a matching alert action and a triage dispatch action
that invokes Ava's new issue_triage skill. The skill instructs Ava
to resolve, convert to board features, delegate, or close issues
with rationale — driving toward zero open issues across all repos.

Domain polls every 5 minutes (issue velocity is low, GitHub rate
limits are a concern with 6+ repos).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/api/github.ts`:
- Around line 489-490: The ghApi call that fetches issues using
`/repos/${repo}/issues?state=open&per_page=100&sort=created&direction=asc` only
retrieves the first page; change the logic in the function containing that ghApi
invocation to implement pagination similar to handleGetPrPipeline(): loop up to
5 pages, call ghApi with a page parameter (e.g., `&page=${page}`), accumulate
results into the existing issues array (or a new aggregated array), break early
if a page returns an empty array or fewer than 100 items, and then use the
aggregated list for downstream counts/processing; ensure you reference and
update the same variable names used around the original ghApi call so other code
continues to work.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2d779bc6-9e8d-4cd5-8f6f-77d6c2012eae

📥 Commits

Reviewing files that changed from the base of the PR and between 9dc4b79 and 58daec5.

📒 Files selected for processing (5)
  • src/api/github.ts
  • src/index.ts
  • workspace/actions.yaml
  • workspace/agents/ava.yaml
  • workspace/goals.yaml
✅ Files skipped from review due to trivial changes (1)
  • src/index.ts

Comment thread src/api/github.ts
Comment on lines +489 to +490
const issues = await ghApi(
`/repos/${repo}/issues?state=open&per_page=100&sort=created&direction=asc`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

No pagination — repos with >100 open issues will be undercounted.

The query fetches only the first 100 issues (per_page=100) without a pagination loop. Compare with handleGetPrPipeline() (lines 142-157) which iterates up to 5 pages. While the "Issue Zero" goal targets <5 total issues, accurate counts matter for triage visibility.

♻️ Suggested pagination pattern (mirroring PR pipeline)
-        const issues = await ghApi(
-          `/repos/${repo}/issues?state=open&per_page=100&sort=created&direction=asc`,
-        ) as Array<{
+        const allIssues: Array<{
           number: number;
           title: string;
           labels: Array<{ name: string }>;
           created_at: string;
           updated_at: string;
           user: { login: string } | null;
           pull_request?: unknown;
-        }>;
+        }> = [];
+        const MAX_PAGES = 5;
+        for (let page = 1; page <= MAX_PAGES; page++) {
+          const batch = await ghApi(
+            `/repos/${repo}/issues?state=open&per_page=100&page=${page}&sort=created&direction=asc`,
+          ) as typeof allIssues;
+          if (batch.length === 0) break;
+          allIssues.push(...batch);
+          if (batch.length < 100) break;
+        }
+        const issues = allIssues;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const issues = await ghApi(
`/repos/${repo}/issues?state=open&per_page=100&sort=created&direction=asc`,
const allIssues: Array<{
number: number;
title: string;
labels: Array<{ name: string }>;
created_at: string;
updated_at: string;
user: { login: string } | null;
pull_request?: unknown;
}> = [];
const MAX_PAGES = 5;
for (let page = 1; page <= MAX_PAGES; page++) {
const batch = await ghApi(
`/repos/${repo}/issues?state=open&per_page=100&page=${page}&sort=created&direction=asc`,
) as typeof allIssues;
if (batch.length === 0) break;
allIssues.push(...batch);
if (batch.length < 100) break;
}
const issues = allIssues;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/github.ts` around lines 489 - 490, The ghApi call that fetches issues
using `/repos/${repo}/issues?state=open&per_page=100&sort=created&direction=asc`
only retrieves the first page; change the logic in the function containing that
ghApi invocation to implement pagination similar to handleGetPrPipeline(): loop
up to 5 pages, call ghApi with a page parameter (e.g., `&page=${page}`),
accumulate results into the existing issues array (or a new aggregated array),
break early if a page returns an empty array or fewer than 100 items, and then
use the aggregated list for downstream counts/processing; ensure you reference
and update the same variable names used around the original ghApi call so other
code continues to work.

)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: manage_board list action (#247) + a2a.trace extension (#359)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): case-insensitive auto-approve prefix matching

"Promote dev to main" titles start with capital P, but the prefix
check was case-sensitive against "promote:". Now lowercases the
title before matching so both "promote:" and "Promote" patterns
are caught.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mabry1985 mabry1985 merged commit 4e910d7 into main Apr 20, 2026
8 checks passed
mabry1985 added a commit that referenced this pull request Apr 20, 2026
* fix: extension URIs use proto-labs.ai (not protolabs.ai) (#407)

All 27 references to https://protolabs.ai/a2a/ext/* changed to
https://proto-labs.ai/a2a/ext/* to match the actual domain. These
URIs are opaque identifiers (not published specs today) but should
reference a domain we own.

Breaking: external agents (Quinn, protoPen) whose cards declare the
old URI will stop matching the registry until they update. Filed on
Quinn to update her card.




* chore(release): bump to v0.7.20 (#408)



* chore: remove protoaudio + protovoice from agent rollcall (#410)

Both services decommissioned. Containers stopped + removed.
Only reference in protoWorkstacean was the rollcall script.

Note: homelab-iac/stacks/ai/docker-compose.yml still has a
worldmonitor network reference at line 521 + service at line 833.
Needs separate cleanup in that repo.




* feat: upgrade web_search → searxng_search + give Ava fleet health tools (#411)

Two changes:

1. Replace the basic `web_search` tool (5 results, hardcoded engines)
   with `searxng_search` — adapted from rabbit-hole.io's full-surface
   SearXNG integration. New capabilities:
   - Category routing: general, news, science, it
   - Time range filtering: day, week, month, year
   - Bang syntax: !wp (Wikipedia), !scholar, !gh (GitHub)
   - Infoboxes, direct answers, suggestions in response
   - Configurable max_results (default 10, was 5)

   Updated in both bus-tools.ts (@protolabsai/sdk pattern) and
   deep-agent-executor.ts (LangChain pattern).

2. Give Ava three fleet health tools she was missing:
   - get_ci_health — CI success rates across repos
   - get_pr_pipeline — open PRs, conflicts, staleness
   - get_incidents — security/ops incidents

   Ava can now answer fleet health questions directly instead of
   always delegating to Quinn.

Ava's tool count: 10 → 13. Tool rename: web_search → searxng_search
(greenfield, no backward compat alias).




* chore(projects): register protoAgent in projects.yaml (#414)

protoAgent is the new GitHub Template repo that replaces per-agent
A2A bootstrapping. Registers it as an active dev project owned by
Quinn, matching the shape of existing entries. Plane / GitHub
webhook / Discord provisioning remain TODO — those integrations
aren't configured in this deployment, so the onboard plugin
skipped them.




* chore(release): bump to v0.7.21 (#413)



* feat(ava): expand helm toolset, wire GOAP skills, fix ceremony disable bug (#415)

Ava agent audit + overhaul:
- Tools: 10 → 22 (direct observation, propose_config_change, incident reporting)
- Skills: 3 → 7 (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills)
- System prompt rewritten: self-improvement instructions, escalation policy, GOAP-dispatch playbook
- DeepAgentExecutor now applies skill-level systemPromptOverride (goal_proposal, diagnose_pr_stuck)
- Fix ceremony loader bug: disabled ceremonies were filtered out, preventing hot-reload from cancelling timers
- Clean up board.pr-audit.yaml (remove spurious action field, restore schedule, keep disabled)
- Update docs: README, deep-agent runtime, agent-skills reference, self-improving loop




* fix(pr-remediator): close dispatch gap — self-dispatch + broaden auto-approve (#417)

Two root causes prevented PRs from being auto-merged:

1. Dispatch gap: tier_0 short-circuit in ActionDispatcherPlugin completed
   all actions immediately without dispatching to agent.skill.request.
   Every action in actions.yaml is tier_0, so the fireAndForget path
   (which publishes the skill request) was unreachable dead code.
   Fix: tier_0 now falls through when meta.fireAndForget is set.

2. Approval gap: readyToMerge requires reviewState=approved, but
   auto-approve only covered dependabot/renovate/promote:/chore(deps.
   Human PRs, release PRs (chore(release), and github-actions PRs
   all lacked approved reviews and sat indefinitely.
   Fix: added app/github-actions to authors, chore(release, chore:,
   docs( to safe title prefixes.

Additionally, PrRemediatorPlugin now self-dispatches remediation on
every world.state.updated tick — checking for readyToMerge, dirty,
failingCi, and changesRequested PRs directly from cached domain data.
This removes the dependency on GOAP dispatch reaching the plugin via
pr.remediate.* topics (which were never published in production after
Arc 1.4 removed meta.topic routing).




* feat(goap): issue_zero domain + goals — track open GitHub issues across fleet (#419)

Adds a github_issues domain that polls /repos/{repo}/issues?state=open
for all managed projects and classifies by label (critical, bug,
enhancement). Three GOAP goals enforce issue hygiene:

  - issues.zero_critical (critical severity, max: 0)
  - issues.zero_bugs (high severity, max: 0)
  - issues.total_low (medium severity, max: 5)

Each goal has a matching alert action and a triage dispatch action
that invokes Ava's new issue_triage skill. The skill instructs Ava
to resolve, convert to board features, delegate, or close issues
with rationale — driving toward zero open issues across all repos.

Domain polls every 5 minutes (issue velocity is low, GitHub rate
limits are a concern with 6+ repos).




* feat: manage_board list action (#247) + a2a.trace extension (#359) (#420)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.




* fix(pr-remediator): case-insensitive auto-approve prefix matching (#421)

* feat: manage_board list action (#247) + a2a.trace extension (#359)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.



* fix(pr-remediator): case-insensitive auto-approve prefix matching

"Promote dev to main" titles start with capital P, but the prefix
check was case-sensitive against "promote:". Now lowercases the
title before matching so both "promote:" and "Promote" patterns
are caught.



---------




---------

Co-authored-by: Josh Mabry <31560031+mabry1985@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Josh <artificialcitizens@gmail.com>
mabry1985 added a commit that referenced this pull request Apr 21, 2026
… (v0.7.22 candidate, re-cut) (#466)

* fix: extension URIs use proto-labs.ai (not protolabs.ai) (#407)

All 27 references to https://protolabs.ai/a2a/ext/* changed to
https://proto-labs.ai/a2a/ext/* to match the actual domain. These
URIs are opaque identifiers (not published specs today) but should
reference a domain we own.

Breaking: external agents (Quinn, protoPen) whose cards declare the
old URI will stop matching the registry until they update. Filed on
Quinn to update her card.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(release): bump to v0.7.20 (#408)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: remove protoaudio + protovoice from agent rollcall (#410)

Both services decommissioned. Containers stopped + removed.
Only reference in protoWorkstacean was the rollcall script.

Note: homelab-iac/stacks/ai/docker-compose.yml still has a
worldmonitor network reference at line 521 + service at line 833.
Needs separate cleanup in that repo.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: upgrade web_search → searxng_search + give Ava fleet health tools (#411)

Two changes:

1. Replace the basic `web_search` tool (5 results, hardcoded engines)
   with `searxng_search` — adapted from rabbit-hole.io's full-surface
   SearXNG integration. New capabilities:
   - Category routing: general, news, science, it
   - Time range filtering: day, week, month, year
   - Bang syntax: !wp (Wikipedia), !scholar, !gh (GitHub)
   - Infoboxes, direct answers, suggestions in response
   - Configurable max_results (default 10, was 5)

   Updated in both bus-tools.ts (@protolabsai/sdk pattern) and
   deep-agent-executor.ts (LangChain pattern).

2. Give Ava three fleet health tools she was missing:
   - get_ci_health — CI success rates across repos
   - get_pr_pipeline — open PRs, conflicts, staleness
   - get_incidents — security/ops incidents

   Ava can now answer fleet health questions directly instead of
   always delegating to Quinn.

Ava's tool count: 10 → 13. Tool rename: web_search → searxng_search
(greenfield, no backward compat alias).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(projects): register protoAgent in projects.yaml (#414)

protoAgent is the new GitHub Template repo that replaces per-agent
A2A bootstrapping. Registers it as an active dev project owned by
Quinn, matching the shape of existing entries. Plane / GitHub
webhook / Discord provisioning remain TODO — those integrations
aren't configured in this deployment, so the onboard plugin
skipped them.

Co-authored-by: Josh <artificialcitizens@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(release): bump to v0.7.21 (#413)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(ava): expand helm toolset, wire GOAP skills, fix ceremony disable bug (#415)

Ava agent audit + overhaul:
- Tools: 10 → 22 (direct observation, propose_config_change, incident reporting)
- Skills: 3 → 7 (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills)
- System prompt rewritten: self-improvement instructions, escalation policy, GOAP-dispatch playbook
- DeepAgentExecutor now applies skill-level systemPromptOverride (goal_proposal, diagnose_pr_stuck)
- Fix ceremony loader bug: disabled ceremonies were filtered out, preventing hot-reload from cancelling timers
- Clean up board.pr-audit.yaml (remove spurious action field, restore schedule, keep disabled)
- Update docs: README, deep-agent runtime, agent-skills reference, self-improving loop

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): close dispatch gap — self-dispatch + broaden auto-approve (#417)

Two root causes prevented PRs from being auto-merged:

1. Dispatch gap: tier_0 short-circuit in ActionDispatcherPlugin completed
   all actions immediately without dispatching to agent.skill.request.
   Every action in actions.yaml is tier_0, so the fireAndForget path
   (which publishes the skill request) was unreachable dead code.
   Fix: tier_0 now falls through when meta.fireAndForget is set.

2. Approval gap: readyToMerge requires reviewState=approved, but
   auto-approve only covered dependabot/renovate/promote:/chore(deps.
   Human PRs, release PRs (chore(release), and github-actions PRs
   all lacked approved reviews and sat indefinitely.
   Fix: added app/github-actions to authors, chore(release, chore:,
   docs( to safe title prefixes.

Additionally, PrRemediatorPlugin now self-dispatches remediation on
every world.state.updated tick — checking for readyToMerge, dirty,
failingCi, and changesRequested PRs directly from cached domain data.
This removes the dependency on GOAP dispatch reaching the plugin via
pr.remediate.* topics (which were never published in production after
Arc 1.4 removed meta.topic routing).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(goap): issue_zero domain + goals — track open GitHub issues across fleet (#419)

Adds a github_issues domain that polls /repos/{repo}/issues?state=open
for all managed projects and classifies by label (critical, bug,
enhancement). Three GOAP goals enforce issue hygiene:

  - issues.zero_critical (critical severity, max: 0)
  - issues.zero_bugs (high severity, max: 0)
  - issues.total_low (medium severity, max: 5)

Each goal has a matching alert action and a triage dispatch action
that invokes Ava's new issue_triage skill. The skill instructs Ava
to resolve, convert to board features, delegate, or close issues
with rationale — driving toward zero open issues across all repos.

Domain polls every 5 minutes (issue velocity is low, GitHub rate
limits are a concern with 6+ repos).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: manage_board list action (#247) + a2a.trace extension (#359) (#420)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): case-insensitive auto-approve prefix matching (#421)

* feat: manage_board list action (#247) + a2a.trace extension (#359)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): case-insensitive auto-approve prefix matching

"Promote dev to main" titles start with capital P, but the prefix
check was case-sensitive against "promote:". Now lowercases the
title before matching so both "promote:" and "Promote" patterns
are caught.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(skill-dispatcher): wire alert.* executors + startup validator (#426) (#427)

Closes the structural gap where 6+ tier_0 fire-and-forget alert skills had
no registered executor, causing SkillDispatcherPlugin to log "No executor
found" and silently drop the dispatch on every GOAP planning cycle.

- AlertSkillExecutorPlugin registers FunctionExecutors for all 24 bare
  alert.* actions in workspace/actions.yaml. Each translates the dispatch
  into a structured message.outbound.discord.alert event consumed by the
  existing WorldEngineAlertPlugin webhook routing.
- validate-action-executors.ts cross-checks the loaded ActionRegistry
  against the live ExecutorRegistry at startup. Surfaces every gap as a
  HIGH-severity Discord alert (goal platform.skills_unwired) and a loud
  console.error. Set WORKSTACEAN_STRICT_WIRING=1 to crash startup instead.
- action.issues_triage_bugs already routes correctly via meta.agentId=ava
  to the existing DeepAgentExecutor for Ava's issue_triage skill — no
  duplicate wiring needed (greenfield).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ceremonies): stop world.state.# leak from ceremony snapshots (#428)

CeremonyStateExtension was publishing { domain, data } envelopes on
`world.state.snapshot` after every ceremony completion. GoalEvaluatorPlugin
subscribes to `world.state.#`, treated the malformed payload as a WorldState,
and emitted a "Selector ... not found" violation for every loaded goal on
every ceremony tick (the cluster of 25+ violations at each :15/:30 boundary
in the live container logs). All listed selectors (flow.efficiency.ratio,
services.discord.connected, agent_health.agentCount, etc.) actually exist
in the producer output — the goals are correct.

Changes:
- Move ceremony snapshot publish to `ceremony.state.snapshot` (off the
  world.state.# namespace). Leaves the existing CeremoniesState shape and
  consumers unchanged.
- Goal evaluator: defensive payload shape check. Reject single-domain
  envelopes ({ domain, data }) and other non-WorldState payloads loud-once
  instead of generating one violation per goal.
- Goal evaluator: startup selector validator. After the first valid world
  state arrives, walk every loaded goal's selector and HIGH-log any that
  doesn't resolve. Re-armed on goals.reload / config.reload so drift caught
  by future hot-reloads also surfaces.
- Tests: regression guard that CeremonyStateExtension does not publish on
  world.state.#; goal evaluator ignores malformed payloads; validator
  catches an intentionally broken selector.

Closes #424

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rollcall): point /rollcall skill at in-repo script (closes #425) (#429)

The Claude Code skill was calling the homelab-iac copy of agent-rollcall.sh,
which had drifted from this repo's copy. The in-repo script knows about
the in-process DeepAgent runtime (Ava, protoBot, Tuner) and the current
A2A fleet; the homelab-iac copy still probed for the archived ava-agent
container and the deprecated protoaudio/protovoice services.

Single source of truth: this repo. The homelab-iac copy was separately
synced in homelab-iac@64e8dcf.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Promote dev to main (v0.7.21) (#418) (#423)

* fix: extension URIs use proto-labs.ai (not protolabs.ai) (#407)

All 27 references to https://protolabs.ai/a2a/ext/* changed to
https://proto-labs.ai/a2a/ext/* to match the actual domain. These
URIs are opaque identifiers (not published specs today) but should
reference a domain we own.

Breaking: external agents (Quinn, protoPen) whose cards declare the
old URI will stop matching the registry until they update. Filed on
Quinn to update her card.




* chore(release): bump to v0.7.20 (#408)



* chore: remove protoaudio + protovoice from agent rollcall (#410)

Both services decommissioned. Containers stopped + removed.
Only reference in protoWorkstacean was the rollcall script.

Note: homelab-iac/stacks/ai/docker-compose.yml still has a
worldmonitor network reference at line 521 + service at line 833.
Needs separate cleanup in that repo.




* feat: upgrade web_search → searxng_search + give Ava fleet health tools (#411)

Two changes:

1. Replace the basic `web_search` tool (5 results, hardcoded engines)
   with `searxng_search` — adapted from rabbit-hole.io's full-surface
   SearXNG integration. New capabilities:
   - Category routing: general, news, science, it
   - Time range filtering: day, week, month, year
   - Bang syntax: !wp (Wikipedia), !scholar, !gh (GitHub)
   - Infoboxes, direct answers, suggestions in response
   - Configurable max_results (default 10, was 5)

   Updated in both bus-tools.ts (@protolabsai/sdk pattern) and
   deep-agent-executor.ts (LangChain pattern).

2. Give Ava three fleet health tools she was missing:
   - get_ci_health — CI success rates across repos
   - get_pr_pipeline — open PRs, conflicts, staleness
   - get_incidents — security/ops incidents

   Ava can now answer fleet health questions directly instead of
   always delegating to Quinn.

Ava's tool count: 10 → 13. Tool rename: web_search → searxng_search
(greenfield, no backward compat alias).




* chore(projects): register protoAgent in projects.yaml (#414)

protoAgent is the new GitHub Template repo that replaces per-agent
A2A bootstrapping. Registers it as an active dev project owned by
Quinn, matching the shape of existing entries. Plane / GitHub
webhook / Discord provisioning remain TODO — those integrations
aren't configured in this deployment, so the onboard plugin
skipped them.




* chore(release): bump to v0.7.21 (#413)



* feat(ava): expand helm toolset, wire GOAP skills, fix ceremony disable bug (#415)

Ava agent audit + overhaul:
- Tools: 10 → 22 (direct observation, propose_config_change, incident reporting)
- Skills: 3 → 7 (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills)
- System prompt rewritten: self-improvement instructions, escalation policy, GOAP-dispatch playbook
- DeepAgentExecutor now applies skill-level systemPromptOverride (goal_proposal, diagnose_pr_stuck)
- Fix ceremony loader bug: disabled ceremonies were filtered out, preventing hot-reload from cancelling timers
- Clean up board.pr-audit.yaml (remove spurious action field, restore schedule, keep disabled)
- Update docs: README, deep-agent runtime, agent-skills reference, self-improving loop




* fix(pr-remediator): close dispatch gap — self-dispatch + broaden auto-approve (#417)

Two root causes prevented PRs from being auto-merged:

1. Dispatch gap: tier_0 short-circuit in ActionDispatcherPlugin completed
   all actions immediately without dispatching to agent.skill.request.
   Every action in actions.yaml is tier_0, so the fireAndForget path
   (which publishes the skill request) was unreachable dead code.
   Fix: tier_0 now falls through when meta.fireAndForget is set.

2. Approval gap: readyToMerge requires reviewState=approved, but
   auto-approve only covered dependabot/renovate/promote:/chore(deps.
   Human PRs, release PRs (chore(release), and github-actions PRs
   all lacked approved reviews and sat indefinitely.
   Fix: added app/github-actions to authors, chore(release, chore:,
   docs( to safe title prefixes.

Additionally, PrRemediatorPlugin now self-dispatches remediation on
every world.state.updated tick — checking for readyToMerge, dirty,
failingCi, and changesRequested PRs directly from cached domain data.
This removes the dependency on GOAP dispatch reaching the plugin via
pr.remediate.* topics (which were never published in production after
Arc 1.4 removed meta.topic routing).




* feat(goap): issue_zero domain + goals — track open GitHub issues across fleet (#419)

Adds a github_issues domain that polls /repos/{repo}/issues?state=open
for all managed projects and classifies by label (critical, bug,
enhancement). Three GOAP goals enforce issue hygiene:

  - issues.zero_critical (critical severity, max: 0)
  - issues.zero_bugs (high severity, max: 0)
  - issues.total_low (medium severity, max: 5)

Each goal has a matching alert action and a triage dispatch action
that invokes Ava's new issue_triage skill. The skill instructs Ava
to resolve, convert to board features, delegate, or close issues
with rationale — driving toward zero open issues across all repos.

Domain polls every 5 minutes (issue velocity is low, GitHub rate
limits are a concern with 6+ repos).




* feat: manage_board list action (#247) + a2a.trace extension (#359) (#420)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.




* fix(pr-remediator): case-insensitive auto-approve prefix matching (#421)

* feat: manage_board list action (#247) + a2a.trace extension (#359)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.



* fix(pr-remediator): case-insensitive auto-approve prefix matching

"Promote dev to main" titles start with capital P, but the prefix
check was case-sensitive against "promote:". Now lowercases the
title before matching so both "promote:" and "Promote" patterns
are caught.



---------




---------

Co-authored-by: Josh Mabry <31560031+mabry1985@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Josh <artificialcitizens@gmail.com>

* feat(ceremony): wire ceremony.security_triage + ceremony.service_health_discord (#431)

Adds CeremonySkillExecutorPlugin — registers FunctionExecutors that bridge
GOAP `ceremony.*` actions to the matching `ceremony.<id>.execute` topic
CeremonyPlugin already listens for. Without this bridge,
SkillDispatcherPlugin dropped every dispatch with "No executor found …"
and (post-#427) emitted HIGH platform.skills_unwired alerts every cycle.

Mirrors the alert-skill-executor-plugin pattern from #427 — explicit
action→ceremony id mapping, install order matters (after registry,
before skill-dispatcher).

Partial fix for #430.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): wire 5 GOAP-dispatched skills + honor hitlPolicy (#432)

Closes the structural gap where 5 actions in workspace/actions.yaml route
to handlers in PrRemediatorPlugin but had no registered executor:
  - action.pr_update_branch    → pr.remediate.update_branch
  - action.pr_merge_ready      → pr.remediate.merge_ready
  - action.pr_fix_ci           → pr.remediate.fix_ci
  - action.pr_address_feedback → pr.remediate.address_feedback
  - action.dispatch_backmerge  → pr.backmerge.dispatch

Before this change SkillDispatcherPlugin logged "No executor found" and
dropped the dispatch every GOAP cycle. After PR #427's startup validator
the same gap raised platform.skills_unwired HIGH every tick.

Wiring follows the AlertSkillExecutorPlugin pattern from #427:
  - PrRemediatorSkillExecutorPlugin registers FunctionExecutors that
    publish on the existing pr-remediator subscription topics, keeping
    "bus is the contract" — no plugin holds a reference to the other.
  - Executors are fire-and-forget per actions.yaml meta. They return a
    successful SkillResult immediately; pr-remediator's handler runs
    asynchronously on the bus subscription.
  - Install order matches alert-skill-executor: AFTER ExecutorRegistry
    construction, BEFORE skill-dispatcher.

For action.pr_merge_ready specifically, the meta.hitlPolicy
(ttlMs: 1800000, onTimeout: approve) is now honoured. The executor
forwards meta into the trigger payload; _handleMergeReady extracts it
via _extractHitlPolicy and passes it to _emitHitlApproval, which
populates HITLRequest.{ttlMs, onTimeout}. HITLPlugin already auto-
publishes a synthetic approve response when onTimeout=approve fires.

Closes part of #430. Ceremony + protoMaker actions ship in a separate PR.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(agents): register protomaker A2A agent (closes part of #430) (#433)

protoMaker (apps/server in protoLabsAI/ava) has been A2A-ready for a while —
serves agent-card.json with 10 skills including the two referenced by
unwired GOAP actions:
  - action.protomaker_triage_blocked → skill board_health
  - action.protomaker_start_auto_mode → skill auto_mode

Both actions targeted [protomaker], but no agent named "protomaker" was
registered, so the dispatcher couldn't route. Adding the entry closes
the routing gap; A2AExecutor's existing target-matching does the rest.

Endpoint: http://automaker-server:3008/a2a (verified from inside the
workstacean container with AVA_API_KEY → JSON-RPC 2.0 response).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(agents): drop overscoped subscribesTo from protomaker entry (#434)

#433 added `subscribesTo: message.inbound.github.#` to the new protomaker
agent registration, copy-pasted from quinn's pattern. That was wrong:
protoMaker is reached via explicit GOAP `targets: [protomaker]` dispatches
(action.protomaker_triage_blocked, action.protomaker_start_auto_mode), not
as a broadcast inbound listener.

Quinn already subscribes to all GitHub inbound and dispatches `bug_triage`
on protoMaker's behalf. Having protomaker subscribe to the same broadcast
topic is one of the contributing paths to the duplicate-triage spam loop
filed as protoLabsAI/protoMaker#3503 (the root cause is Quinn's handler
not being idempotent — but this cleanup removes one extra firing path).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ava): rename web_search → searxng_search to match runtime tool name (#435)

PR #411 renamed the tool from web_search to searxng_search in
src/agent-runtime/tools/bus-tools.ts (line 393), but ava.yaml still
declared the old name. Result: at startup the runtime warns
"agent ava declares unknown tools: web_search" and Ava ends up with no
search capability — when asked, she explicitly responds "I don't have a
searxng_search or web_search tool in my current toolkit."

This is the config side of the half-finished rename that PR #411 missed.
After this lands and workstacean restarts, Ava's toolkit should include
searxng_search and the unknown-tools warning should be empty for her.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(goap): disable fleet_agent_stuck loop — fires every cycle, no dedup (#436)

Two GOAP actions on goal fleet.no_agent_stuck were spamming Discord
because they had no `effects` and no cooldown:

  - alert.fleet_agent_stuck → posts a Discord alert
  - action.fleet_incident_response → dispatches Ava to file an incident,
    page on-call, and pause routing

Observed 2026-04-20: when auto-triage-sweep hit 100% failure rate on
bug_triage (cascading from the non-idempotent handler in
protoLabsAI/protoMaker#3503), GOAP re-fired both actions every planning
cycle. Ava filed INC-003 through INC-009 in ~30 seconds, each posting to
Discord. The pause routing succeeded but the rolling 1h failure rate
metric doesn't drop instantly, so the goal stayed violated and the loop
kept re-firing.

Disabling both actions until proper dedup lands. Reinstate when:
  1. action.fleet_incident_response gains a cooldown OR an `effects`
     marker that satisfies the goal for the cooldown window
  2. Ava's fleet_incident_response skill checks for an existing open
     incident on the same agent before filing a new one
  3. alert.fleet_agent_stuck gains per-agent rate limiting

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(goap): disable 4 more action.* loops — same no-cooldown bug as #436 (#451)

After #436 disabled action.fleet_incident_response, observed similar
loop spam from FOUR more action.* dispatches that share the same
architectural bug (no cooldown, no satisfying effects → re-fire every
GOAP cycle while goal stays violated):

  action.fleet_investigate_orphaned_skills  — Ava posts orphaned-skill
                                               diagnosis to Discord ops
                                               on every cycle (8+ posts
                                               in 1 min observed)
  action.issues_triage_critical             — ~447 fires in 30 min
  action.issues_triage_bugs                 — ~447 fires in 30 min;
                                               compounds with #3503
                                               by re-triaging same issues
  action.fleet_downshift_models             — same pattern when cost
                                               exceeds budget

ALL share the same pattern as the alert.* actions and the original
fleet_incident_response: effects: [] + no cooldownMs + persistent goal
violation = infinite re-fire.

Mitigation only — Ava temporarily can't auto-investigate orphaned
skills or auto-triage new GitHub issues. Reinstate when issue #437
ships action-level cooldown (meta.cooldownMs) or proper effects-with-TTL.
The alert.* siblings (24 of them) also have this bug but are
non-impacting today because DISCORD_WEBHOOK_ALERTS is unset and
WorldEngineAlertPlugin drops them silently — fixing #437 will cover
both at the dispatcher level.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(goap): per-action cooldown in ActionDispatcher (closes #437) (#452)

GOAP actions with `effects: []` re-fired every planning cycle (~3s) while
their goal stayed violated. Two prod fires (PR #436, PR #451) had to
disable 6 actions before this lands. Restoring autonomy.

ActionDispatcherPlugin now honors `meta.cooldownMs` on every action. When
an action with a positive cooldownMs fires, the dispatcher records its
timestamp; subsequent dispatches of the same action id within the window
are dropped BEFORE the WIP queue and BEFORE the executor. Single chokepoint
covers both alert.* (FunctionExecutor → Discord) and action.* (DeepAgent /
A2A) paths. Drops log a fail-fast diagnostic with action id, age, and
remaining window, plus bump a new `cooldown_dropped` telemetry event.

Cooldown bucket is keyed on action id alone — per-target keying isn't
needed because each GOAP action targets one situation. Greenfield shape:
absence of `meta.cooldownMs` means "no cooldown" naturally, no flag.

Defaults applied to workspace/actions.yaml:
  - alert.*                          → 15 min
  - action.* with skillHint          → 30 min (agent work is expensive)
  - action.pr_*                      → 5 min  (remediation must stay responsive)
  - ceremony.*                       → 30 min (treated as skill dispatch)
  - action.dispatch_backmerge        → none (in-handler per-repo cooldown
                                              in pr-remediator stays authoritative)

Re-enables the 6 actions disabled by PRs #436 and #451:
alert.fleet_agent_stuck, action.fleet_incident_response,
action.fleet_downshift_models, action.fleet_investigate_orphaned_skills,
action.issues_triage_critical, action.issues_triage_bugs.

Tests:
  - action-dispatcher unit tests cover: blocks repeats within window;
    A's cooldown does not affect B; window expiry admits next dispatch;
    absent cooldownMs and cooldownMs<=0 mean no throttling.
  - End-to-end test spam-publishes 100 violations of fleet.no_skill_orphaned
    and asserts exactly 1 dispatch reaches the executor.
  - bun test: 1023 / 1023 pass.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ceremonies): route 4 ceremonies to right skill owner, disable 3 broken (#454)

Audit of all 10 workspace/ceremonies/*.yaml against the live agent skill
registry surfaced 7 with skill-target mismatches that fail every fire:

ROUTED to correct skill owner:
  board.cleanup     skill=board_audit    targets [all] → [quinn]
  board.health      skill=board_health   targets [all] → [protomaker]
  daily-standup     skill=board_audit    targets [ava] → [quinn]
  health-check      skill=board_audit    targets [ava] → [quinn]

DISABLED (no agent advertises the skill):
  agent-health      skill=health_check   — no health_check anywhere
  board.retro       skill=pattern_analysis — no pattern_analysis anywhere
  service-health    skill=health_check   — same as agent-health

Quinn owns board_audit (and bug_triage / pr_review / qa_report).
Protomaker owns board_health (and 9 other apps/server skills).
The two `health_check`-keyed ceremonies were redundant anyway — the
agent_health and services world-state domains poll the same data every
60s and expose it on /api/agent-health and /api/services.

Re-enable the disabled three with a real skill if a periodic ANNOUNCE
to Discord is wanted (small `*_health_report` skill on protobot would
do it).

Workspace is bind-mounted, so the live container picks this up on
restart with no rebuild.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(goap): pre-dispatch target registry guard (closes #444) (#455)

The GOAP planner could dispatch a skill to a target agent (e.g.
auto-triage-sweep, user) where the named target wasn't in the live
ExecutorRegistry. The dispatcher fired anyway, the executor errored
404-style, and the failure cascaded into stuck work items + duplicate
incident filings (INC-003 through INC-018, ~93 work items in error
state). The cooldown work in #437 / #452 masked the symptom but the
structural gap remained.

ActionDispatcherPlugin now takes the shared ExecutorRegistry handle and
runs `_admitOrTargetUnresolved` immediately after the cooldown check and
BEFORE the WIP queue / executor. Same chokepoint pattern as cooldown:

  - target absent (skill-routed)            → admit
  - target = "all" (broadcast sentinel)     → admit
  - target matches a registration agentName → admit
  - target unresolvable                     → drop, log loud, telemetry
                                              bump `target_unresolved`

Drops surface action id, the unresolvable target, AND the agents that
DO exist so the routing mistake is immediately diagnosable. The only
opt-out is the broadcast sentinel "all" — there is no flag, no
enabled-bool. Greenfield-strict shape.

Wiring: src/index.ts passes the shared executorRegistry into the
dispatcher factory. Test fixtures that don't exercise target routing
omit the registry and the check is skipped — so existing tests stay
green without modification.

Audit of workspace/actions.yaml: only `protomaker` appears as an active
agentId target (twice). That agent is registered in workspace/agents.yaml.
The historical bad targets (`auto-triage-sweep`, `user`) were removed by
prior cleanup; this PR ensures any future regression fails closed.

Tests added in src/plugins/action-dispatcher-plugin.test.ts:
  - admits when target is registered
  - drops when target is unregistered + bumps `target_unresolved`
  - drops on mixed-target intent (single-target shape today via meta.agentId)
  - admits when target is the "all" broadcast sentinel
  - admits when meta.agentId is absent (skill-routed dispatch)
  - admits when no registry is wired (legacy test fixtures)

bun test: 1029 / 1029 pass.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ceremony): honor enabled:false on initial load (closes #453) (#456)

PR #415 fixed the hot-reload path so flipping a ceremony to disabled
cancelled its timer, but the initial-load path still added every YAML
entry (disabled or not) to the in-memory registry. Two consequences:

  1. External `ceremony.<id>.execute` triggers (from
     CeremonySkillExecutorPlugin's GOAP bridge) found the disabled
     ceremony in the registry and fired it anyway.
  2. After hot-reload flipped a ceremony enabled→disabled, the entry
     stayed in the registry — same external-trigger leak.

Fix: filter `enabled === false` at every place that lands a ceremony in
the registry (initial install, hot-reload new-file path, hot-reload
changed-file path). Disabled ceremonies are loaded by the YAML parser
(so the changed-file path can detect a flip) but never reach the
registry, never schedule a timer, and cannot be resurrected by an
external trigger. Operators see a `Skipping disabled ceremony: <id>`
log line for each skip — fail-loud per project convention.

Greenfield: no flag, no toggle. enabled:false means disabled
everywhere.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(tests): wrap subscribe-spy push callbacks to return void (#457)

13 TS2322 errors snuck through #452 and #455 because the CI test job
has been failing on type-check while build-and-push (the gate that
actually publishes :dev) is a separate workflow that runs on push.
Result: main/dev were green for container publish even though tsc
--noEmit was returning exit 2. Not visible in PR merge gates either
because test.conclusion=failure + build-and-push.conclusion=skipping
still resolved to a mergeable state.

Pattern of the 13 errors:

  bus.subscribe(T, "spy", (m) => requests.push(m));
                                  ^^^^^^^^^^^^^^^^
  Array.push() returns number, but the subscribe callback expects
  void | Promise<void>. Fix: wrap the body in a block so the arrow
  returns void:

  bus.subscribe(T, "spy", (m) => { requests.push(m); });

Applied via sed-style regex across both test files. 1029 tests still
pass (bun test). `bun run tsc --noEmit` now exits 0.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(fleet-health): filter outcomes from synthetic actors via registry whitelist (closes #459) (#460)

AgentFleetHealthPlugin now takes an optional ExecutorRegistry (mirrors the
#455 wiring on ActionDispatcherPlugin). On each inbound autonomous.outcome,
`systemActor` is checked against `executorRegistry.list().map(r => r.agentName)`:

- Registered agent (ava, quinn, protomaker, ...) → aggregated in agents[]
  (existing shape, no behavior change).
- Anything else (pr-remediator, auto-triage-sweep, goap, user, ...) →
  routed to a separate systemActors[] bucket. No longer pollutes
  agentCount / maxFailureRate1h / orphanedSkillCount, so Ava's sitreps
  stop surfacing plugin names as "stuck agents".

First time a synthetic actor is seen, the plugin emits a one-time
console.warn naming the actor + skill (fail-fast and loud, per policy)
so operators know what's being filtered. No flag — same greenfield /
chokepoint discipline as #437 (cooldown) and #444 (target guard).

Scope note on `_default`: this plugin keys on outcome `systemActor`,
not registry `agentName`. `_default` only appears in /api/agent-health
(the registry-driven view). Nothing currently publishes an outcome with
`systemActor: "_default"`, so it doesn't reach agents[] here. If it
ever did, the new whitelist would drop it to systemActors[] — the right
outcome.

Verification plan (post-deploy):
  curl -s -X POST http://localhost:3000/v1/chat/completions \\
    -H 'Content-Type: application/json' \\
    -d '{"model":"ava","messages":[{"role":"user","content":"fleet sitrep"}]}'

Expected: no `pr-remediator`, `auto-triage-sweep`, or `user` in agents[].

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(a2a): agent card advertises canonical A2A endpoint, not the dashboard URL (#462)

The card at /.well-known/agent-card.json was advertising a URL like
http://ava:8081/a2a — host-mapped to the Astro dashboard, which 404s on
/a2a. Spec-compliant clients (@a2a-js/sdk and friends) doing card
discovery → POST to card.url could not reach the actual A2A endpoint;
the voice agent team papered over this by switching to the
/v1/chat/completions shim.

Fix: derive the card's `url` from variables that describe where the A2A
endpoint actually lives.

  1. WORKSTACEAN_PUBLIC_BASE_URL (e.g. https://ava.proto-labs.ai) →
     ${publicBase}/a2a. The canonical Cloudflare-fronted URL for
     external/Tailscale callers.
  2. Otherwise, http://${WORKSTACEAN_INTERNAL_HOST ?? "workstacean"}:${WORKSTACEAN_HTTP_PORT}/a2a
     — docker-network service name + the actual API port.

Also populate `additionalInterfaces` with the JSON-RPC transport at the
same URL so spec-compliant clients can pick deterministically. Drop the
WORKSTACEAN_BASE_URL coupling in the card builder — that variable
remains the externally-reachable URL stamped into A2A push-notification
callbacks (different concern, separate documentation).

Closes #461

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Josh <artificialcitizens@gmail.com>
mabry1985 added a commit that referenced this pull request Apr 22, 2026
… (v0.7.22 candidate, re-cut) (#466) (#468)

* fix: extension URIs use proto-labs.ai (not protolabs.ai) (#407)

All 27 references to https://protolabs.ai/a2a/ext/* changed to
https://proto-labs.ai/a2a/ext/* to match the actual domain. These
URIs are opaque identifiers (not published specs today) but should
reference a domain we own.

Breaking: external agents (Quinn, protoPen) whose cards declare the
old URI will stop matching the registry until they update. Filed on
Quinn to update her card.




* chore(release): bump to v0.7.20 (#408)



* chore: remove protoaudio + protovoice from agent rollcall (#410)

Both services decommissioned. Containers stopped + removed.
Only reference in protoWorkstacean was the rollcall script.

Note: homelab-iac/stacks/ai/docker-compose.yml still has a
worldmonitor network reference at line 521 + service at line 833.
Needs separate cleanup in that repo.




* feat: upgrade web_search → searxng_search + give Ava fleet health tools (#411)

Two changes:

1. Replace the basic `web_search` tool (5 results, hardcoded engines)
   with `searxng_search` — adapted from rabbit-hole.io's full-surface
   SearXNG integration. New capabilities:
   - Category routing: general, news, science, it
   - Time range filtering: day, week, month, year
   - Bang syntax: !wp (Wikipedia), !scholar, !gh (GitHub)
   - Infoboxes, direct answers, suggestions in response
   - Configurable max_results (default 10, was 5)

   Updated in both bus-tools.ts (@protolabsai/sdk pattern) and
   deep-agent-executor.ts (LangChain pattern).

2. Give Ava three fleet health tools she was missing:
   - get_ci_health — CI success rates across repos
   - get_pr_pipeline — open PRs, conflicts, staleness
   - get_incidents — security/ops incidents

   Ava can now answer fleet health questions directly instead of
   always delegating to Quinn.

Ava's tool count: 10 → 13. Tool rename: web_search → searxng_search
(greenfield, no backward compat alias).




* chore(projects): register protoAgent in projects.yaml (#414)

protoAgent is the new GitHub Template repo that replaces per-agent
A2A bootstrapping. Registers it as an active dev project owned by
Quinn, matching the shape of existing entries. Plane / GitHub
webhook / Discord provisioning remain TODO — those integrations
aren't configured in this deployment, so the onboard plugin
skipped them.




* chore(release): bump to v0.7.21 (#413)



* feat(ava): expand helm toolset, wire GOAP skills, fix ceremony disable bug (#415)

Ava agent audit + overhaul:
- Tools: 10 → 22 (direct observation, propose_config_change, incident reporting)
- Skills: 3 → 7 (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills)
- System prompt rewritten: self-improvement instructions, escalation policy, GOAP-dispatch playbook
- DeepAgentExecutor now applies skill-level systemPromptOverride (goal_proposal, diagnose_pr_stuck)
- Fix ceremony loader bug: disabled ceremonies were filtered out, preventing hot-reload from cancelling timers
- Clean up board.pr-audit.yaml (remove spurious action field, restore schedule, keep disabled)
- Update docs: README, deep-agent runtime, agent-skills reference, self-improving loop




* fix(pr-remediator): close dispatch gap — self-dispatch + broaden auto-approve (#417)

Two root causes prevented PRs from being auto-merged:

1. Dispatch gap: tier_0 short-circuit in ActionDispatcherPlugin completed
   all actions immediately without dispatching to agent.skill.request.
   Every action in actions.yaml is tier_0, so the fireAndForget path
   (which publishes the skill request) was unreachable dead code.
   Fix: tier_0 now falls through when meta.fireAndForget is set.

2. Approval gap: readyToMerge requires reviewState=approved, but
   auto-approve only covered dependabot/renovate/promote:/chore(deps.
   Human PRs, release PRs (chore(release), and github-actions PRs
   all lacked approved reviews and sat indefinitely.
   Fix: added app/github-actions to authors, chore(release, chore:,
   docs( to safe title prefixes.

Additionally, PrRemediatorPlugin now self-dispatches remediation on
every world.state.updated tick — checking for readyToMerge, dirty,
failingCi, and changesRequested PRs directly from cached domain data.
This removes the dependency on GOAP dispatch reaching the plugin via
pr.remediate.* topics (which were never published in production after
Arc 1.4 removed meta.topic routing).




* feat(goap): issue_zero domain + goals — track open GitHub issues across fleet (#419)

Adds a github_issues domain that polls /repos/{repo}/issues?state=open
for all managed projects and classifies by label (critical, bug,
enhancement). Three GOAP goals enforce issue hygiene:

  - issues.zero_critical (critical severity, max: 0)
  - issues.zero_bugs (high severity, max: 0)
  - issues.total_low (medium severity, max: 5)

Each goal has a matching alert action and a triage dispatch action
that invokes Ava's new issue_triage skill. The skill instructs Ava
to resolve, convert to board features, delegate, or close issues
with rationale — driving toward zero open issues across all repos.

Domain polls every 5 minutes (issue velocity is low, GitHub rate
limits are a concern with 6+ repos).




* feat: manage_board list action (#247) + a2a.trace extension (#359) (#420)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.




* fix(pr-remediator): case-insensitive auto-approve prefix matching (#421)

* feat: manage_board list action (#247) + a2a.trace extension (#359)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.



* fix(pr-remediator): case-insensitive auto-approve prefix matching

"Promote dev to main" titles start with capital P, but the prefix
check was case-sensitive against "promote:". Now lowercases the
title before matching so both "promote:" and "Promote" patterns
are caught.



---------




* fix(skill-dispatcher): wire alert.* executors + startup validator (#426) (#427)

Closes the structural gap where 6+ tier_0 fire-and-forget alert skills had
no registered executor, causing SkillDispatcherPlugin to log "No executor
found" and silently drop the dispatch on every GOAP planning cycle.

- AlertSkillExecutorPlugin registers FunctionExecutors for all 24 bare
  alert.* actions in workspace/actions.yaml. Each translates the dispatch
  into a structured message.outbound.discord.alert event consumed by the
  existing WorldEngineAlertPlugin webhook routing.
- validate-action-executors.ts cross-checks the loaded ActionRegistry
  against the live ExecutorRegistry at startup. Surfaces every gap as a
  HIGH-severity Discord alert (goal platform.skills_unwired) and a loud
  console.error. Set WORKSTACEAN_STRICT_WIRING=1 to crash startup instead.
- action.issues_triage_bugs already routes correctly via meta.agentId=ava
  to the existing DeepAgentExecutor for Ava's issue_triage skill — no
  duplicate wiring needed (greenfield).




* fix(ceremonies): stop world.state.# leak from ceremony snapshots (#428)

CeremonyStateExtension was publishing { domain, data } envelopes on
`world.state.snapshot` after every ceremony completion. GoalEvaluatorPlugin
subscribes to `world.state.#`, treated the malformed payload as a WorldState,
and emitted a "Selector ... not found" violation for every loaded goal on
every ceremony tick (the cluster of 25+ violations at each :15/:30 boundary
in the live container logs). All listed selectors (flow.efficiency.ratio,
services.discord.connected, agent_health.agentCount, etc.) actually exist
in the producer output — the goals are correct.

Changes:
- Move ceremony snapshot publish to `ceremony.state.snapshot` (off the
  world.state.# namespace). Leaves the existing CeremoniesState shape and
  consumers unchanged.
- Goal evaluator: defensive payload shape check. Reject single-domain
  envelopes ({ domain, data }) and other non-WorldState payloads loud-once
  instead of generating one violation per goal.
- Goal evaluator: startup selector validator. After the first valid world
  state arrives, walk every loaded goal's selector and HIGH-log any that
  doesn't resolve. Re-armed on goals.reload / config.reload so drift caught
  by future hot-reloads also surfaces.
- Tests: regression guard that CeremonyStateExtension does not publish on
  world.state.#; goal evaluator ignores malformed payloads; validator
  catches an intentionally broken selector.

Closes #424




* fix(rollcall): point /rollcall skill at in-repo script (closes #425) (#429)

The Claude Code skill was calling the homelab-iac copy of agent-rollcall.sh,
which had drifted from this repo's copy. The in-repo script knows about
the in-process DeepAgent runtime (Ava, protoBot, Tuner) and the current
A2A fleet; the homelab-iac copy still probed for the archived ava-agent
container and the deprecated protoaudio/protovoice services.

Single source of truth: this repo. The homelab-iac copy was separately
synced in homelab-iac@64e8dcf.




* Promote dev to main (v0.7.21) (#418) (#423)

* fix: extension URIs use proto-labs.ai (not protolabs.ai) (#407)

All 27 references to https://protolabs.ai/a2a/ext/* changed to
https://proto-labs.ai/a2a/ext/* to match the actual domain. These
URIs are opaque identifiers (not published specs today) but should
reference a domain we own.

Breaking: external agents (Quinn, protoPen) whose cards declare the
old URI will stop matching the registry until they update. Filed on
Quinn to update her card.




* chore(release): bump to v0.7.20 (#408)



* chore: remove protoaudio + protovoice from agent rollcall (#410)

Both services decommissioned. Containers stopped + removed.
Only reference in protoWorkstacean was the rollcall script.

Note: homelab-iac/stacks/ai/docker-compose.yml still has a
worldmonitor network reference at line 521 + service at line 833.
Needs separate cleanup in that repo.




* feat: upgrade web_search → searxng_search + give Ava fleet health tools (#411)

Two changes:

1. Replace the basic `web_search` tool (5 results, hardcoded engines)
   with `searxng_search` — adapted from rabbit-hole.io's full-surface
   SearXNG integration. New capabilities:
   - Category routing: general, news, science, it
   - Time range filtering: day, week, month, year
   - Bang syntax: !wp (Wikipedia), !scholar, !gh (GitHub)
   - Infoboxes, direct answers, suggestions in response
   - Configurable max_results (default 10, was 5)

   Updated in both bus-tools.ts (@protolabsai/sdk pattern) and
   deep-agent-executor.ts (LangChain pattern).

2. Give Ava three fleet health tools she was missing:
   - get_ci_health — CI success rates across repos
   - get_pr_pipeline — open PRs, conflicts, staleness
   - get_incidents — security/ops incidents

   Ava can now answer fleet health questions directly instead of
   always delegating to Quinn.

Ava's tool count: 10 → 13. Tool rename: web_search → searxng_search
(greenfield, no backward compat alias).




* chore(projects): register protoAgent in projects.yaml (#414)

protoAgent is the new GitHub Template repo that replaces per-agent
A2A bootstrapping. Registers it as an active dev project owned by
Quinn, matching the shape of existing entries. Plane / GitHub
webhook / Discord provisioning remain TODO — those integrations
aren't configured in this deployment, so the onboard plugin
skipped them.




* chore(release): bump to v0.7.21 (#413)



* feat(ava): expand helm toolset, wire GOAP skills, fix ceremony disable bug (#415)

Ava agent audit + overhaul:
- Tools: 10 → 22 (direct observation, propose_config_change, incident reporting)
- Skills: 3 → 7 (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills)
- System prompt rewritten: self-improvement instructions, escalation policy, GOAP-dispatch playbook
- DeepAgentExecutor now applies skill-level systemPromptOverride (goal_proposal, diagnose_pr_stuck)
- Fix ceremony loader bug: disabled ceremonies were filtered out, preventing hot-reload from cancelling timers
- Clean up board.pr-audit.yaml (remove spurious action field, restore schedule, keep disabled)
- Update docs: README, deep-agent runtime, agent-skills reference, self-improving loop




* fix(pr-remediator): close dispatch gap — self-dispatch + broaden auto-approve (#417)

Two root causes prevented PRs from being auto-merged:

1. Dispatch gap: tier_0 short-circuit in ActionDispatcherPlugin completed
   all actions immediately without dispatching to agent.skill.request.
   Every action in actions.yaml is tier_0, so the fireAndForget path
   (which publishes the skill request) was unreachable dead code.
   Fix: tier_0 now falls through when meta.fireAndForget is set.

2. Approval gap: readyToMerge requires reviewState=approved, but
   auto-approve only covered dependabot/renovate/promote:/chore(deps.
   Human PRs, release PRs (chore(release), and github-actions PRs
   all lacked approved reviews and sat indefinitely.
   Fix: added app/github-actions to authors, chore(release, chore:,
   docs( to safe title prefixes.

Additionally, PrRemediatorPlugin now self-dispatches remediation on
every world.state.updated tick — checking for readyToMerge, dirty,
failingCi, and changesRequested PRs directly from cached domain data.
This removes the dependency on GOAP dispatch reaching the plugin via
pr.remediate.* topics (which were never published in production after
Arc 1.4 removed meta.topic routing).




* feat(goap): issue_zero domain + goals — track open GitHub issues across fleet (#419)

Adds a github_issues domain that polls /repos/{repo}/issues?state=open
for all managed projects and classifies by label (critical, bug,
enhancement). Three GOAP goals enforce issue hygiene:

  - issues.zero_critical (critical severity, max: 0)
  - issues.zero_bugs (high severity, max: 0)
  - issues.total_low (medium severity, max: 5)

Each goal has a matching alert action and a triage dispatch action
that invokes Ava's new issue_triage skill. The skill instructs Ava
to resolve, convert to board features, delegate, or close issues
with rationale — driving toward zero open issues across all repos.

Domain polls every 5 minutes (issue velocity is low, GitHub rate
limits are a concern with 6+ repos).




* feat: manage_board list action (#247) + a2a.trace extension (#359) (#420)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.




* fix(pr-remediator): case-insensitive auto-approve prefix matching (#421)

* feat: manage_board list action (#247) + a2a.trace extension (#359)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.



* fix(pr-remediator): case-insensitive auto-approve prefix matching

"Promote dev to main" titles start with capital P, but the prefix
check was case-sensitive against "promote:". Now lowercases the
title before matching so both "promote:" and "Promote" patterns
are caught.



---------




---------








* feat(ceremony): wire ceremony.security_triage + ceremony.service_health_discord (#431)

Adds CeremonySkillExecutorPlugin — registers FunctionExecutors that bridge
GOAP `ceremony.*` actions to the matching `ceremony.<id>.execute` topic
CeremonyPlugin already listens for. Without this bridge,
SkillDispatcherPlugin dropped every dispatch with "No executor found …"
and (post-#427) emitted HIGH platform.skills_unwired alerts every cycle.

Mirrors the alert-skill-executor-plugin pattern from #427 — explicit
action→ceremony id mapping, install order matters (after registry,
before skill-dispatcher).

Partial fix for #430.




* fix(pr-remediator): wire 5 GOAP-dispatched skills + honor hitlPolicy (#432)

Closes the structural gap where 5 actions in workspace/actions.yaml route
to handlers in PrRemediatorPlugin but had no registered executor:
  - action.pr_update_branch    → pr.remediate.update_branch
  - action.pr_merge_ready      → pr.remediate.merge_ready
  - action.pr_fix_ci           → pr.remediate.fix_ci
  - action.pr_address_feedback → pr.remediate.address_feedback
  - action.dispatch_backmerge  → pr.backmerge.dispatch

Before this change SkillDispatcherPlugin logged "No executor found" and
dropped the dispatch every GOAP cycle. After PR #427's startup validator
the same gap raised platform.skills_unwired HIGH every tick.

Wiring follows the AlertSkillExecutorPlugin pattern from #427:
  - PrRemediatorSkillExecutorPlugin registers FunctionExecutors that
    publish on the existing pr-remediator subscription topics, keeping
    "bus is the contract" — no plugin holds a reference to the other.
  - Executors are fire-and-forget per actions.yaml meta. They return a
    successful SkillResult immediately; pr-remediator's handler runs
    asynchronously on the bus subscription.
  - Install order matches alert-skill-executor: AFTER ExecutorRegistry
    construction, BEFORE skill-dispatcher.

For action.pr_merge_ready specifically, the meta.hitlPolicy
(ttlMs: 1800000, onTimeout: approve) is now honoured. The executor
forwards meta into the trigger payload; _handleMergeReady extracts it
via _extractHitlPolicy and passes it to _emitHitlApproval, which
populates HITLRequest.{ttlMs, onTimeout}. HITLPlugin already auto-
publishes a synthetic approve response when onTimeout=approve fires.

Closes part of #430. Ceremony + protoMaker actions ship in a separate PR.




* feat(agents): register protomaker A2A agent (closes part of #430) (#433)

protoMaker (apps/server in protoLabsAI/ava) has been A2A-ready for a while —
serves agent-card.json with 10 skills including the two referenced by
unwired GOAP actions:
  - action.protomaker_triage_blocked → skill board_health
  - action.protomaker_start_auto_mode → skill auto_mode

Both actions targeted [protomaker], but no agent named "protomaker" was
registered, so the dispatcher couldn't route. Adding the entry closes
the routing gap; A2AExecutor's existing target-matching does the rest.

Endpoint: http://automaker-server:3008/a2a (verified from inside the
workstacean container with AVA_API_KEY → JSON-RPC 2.0 response).




* fix(agents): drop overscoped subscribesTo from protomaker entry (#434)

#433 added `subscribesTo: message.inbound.github.#` to the new protomaker
agent registration, copy-pasted from quinn's pattern. That was wrong:
protoMaker is reached via explicit GOAP `targets: [protomaker]` dispatches
(action.protomaker_triage_blocked, action.protomaker_start_auto_mode), not
as a broadcast inbound listener.

Quinn already subscribes to all GitHub inbound and dispatches `bug_triage`
on protoMaker's behalf. Having protomaker subscribe to the same broadcast
topic is one of the contributing paths to the duplicate-triage spam loop
filed as protoLabsAI/protoMaker#3503 (the root cause is Quinn's handler
not being idempotent — but this cleanup removes one extra firing path).




* fix(ava): rename web_search → searxng_search to match runtime tool name (#435)

PR #411 renamed the tool from web_search to searxng_search in
src/agent-runtime/tools/bus-tools.ts (line 393), but ava.yaml still
declared the old name. Result: at startup the runtime warns
"agent ava declares unknown tools: web_search" and Ava ends up with no
search capability — when asked, she explicitly responds "I don't have a
searxng_search or web_search tool in my current toolkit."

This is the config side of the half-finished rename that PR #411 missed.
After this lands and workstacean restarts, Ava's toolkit should include
searxng_search and the unknown-tools warning should be empty for her.




* fix(goap): disable fleet_agent_stuck loop — fires every cycle, no dedup (#436)

Two GOAP actions on goal fleet.no_agent_stuck were spamming Discord
because they had no `effects` and no cooldown:

  - alert.fleet_agent_stuck → posts a Discord alert
  - action.fleet_incident_response → dispatches Ava to file an incident,
    page on-call, and pause routing

Observed 2026-04-20: when auto-triage-sweep hit 100% failure rate on
bug_triage (cascading from the non-idempotent handler in
protoLabsAI/protoMaker#3503), GOAP re-fired both actions every planning
cycle. Ava filed INC-003 through INC-009 in ~30 seconds, each posting to
Discord. The pause routing succeeded but the rolling 1h failure rate
metric doesn't drop instantly, so the goal stayed violated and the loop
kept re-firing.

Disabling both actions until proper dedup lands. Reinstate when:
  1. action.fleet_incident_response gains a cooldown OR an `effects`
     marker that satisfies the goal for the cooldown window
  2. Ava's fleet_incident_response skill checks for an existing open
     incident on the same agent before filing a new one
  3. alert.fleet_agent_stuck gains per-agent rate limiting




* fix(goap): disable 4 more action.* loops — same no-cooldown bug as #436 (#451)

After #436 disabled action.fleet_incident_response, observed similar
loop spam from FOUR more action.* dispatches that share the same
architectural bug (no cooldown, no satisfying effects → re-fire every
GOAP cycle while goal stays violated):

  action.fleet_investigate_orphaned_skills  — Ava posts orphaned-skill
                                               diagnosis to Discord ops
                                               on every cycle (8+ posts
                                               in 1 min observed)
  action.issues_triage_critical             — ~447 fires in 30 min
  action.issues_triage_bugs                 — ~447 fires in 30 min;
                                               compounds with #3503
                                               by re-triaging same issues
  action.fleet_downshift_models             — same pattern when cost
                                               exceeds budget

ALL share the same pattern as the alert.* actions and the original
fleet_incident_response: effects: [] + no cooldownMs + persistent goal
violation = infinite re-fire.

Mitigation only — Ava temporarily can't auto-investigate orphaned
skills or auto-triage new GitHub issues. Reinstate when issue #437
ships action-level cooldown (meta.cooldownMs) or proper effects-with-TTL.
The alert.* siblings (24 of them) also have this bug but are
non-impacting today because DISCORD_WEBHOOK_ALERTS is unset and
WorldEngineAlertPlugin drops them silently — fixing #437 will cover
both at the dispatcher level.




* feat(goap): per-action cooldown in ActionDispatcher (closes #437) (#452)

GOAP actions with `effects: []` re-fired every planning cycle (~3s) while
their goal stayed violated. Two prod fires (PR #436, PR #451) had to
disable 6 actions before this lands. Restoring autonomy.

ActionDispatcherPlugin now honors `meta.cooldownMs` on every action. When
an action with a positive cooldownMs fires, the dispatcher records its
timestamp; subsequent dispatches of the same action id within the window
are dropped BEFORE the WIP queue and BEFORE the executor. Single chokepoint
covers both alert.* (FunctionExecutor → Discord) and action.* (DeepAgent /
A2A) paths. Drops log a fail-fast diagnostic with action id, age, and
remaining window, plus bump a new `cooldown_dropped` telemetry event.

Cooldown bucket is keyed on action id alone — per-target keying isn't
needed because each GOAP action targets one situation. Greenfield shape:
absence of `meta.cooldownMs` means "no cooldown" naturally, no flag.

Defaults applied to workspace/actions.yaml:
  - alert.*                          → 15 min
  - action.* with skillHint          → 30 min (agent work is expensive)
  - action.pr_*                      → 5 min  (remediation must stay responsive)
  - ceremony.*                       → 30 min (treated as skill dispatch)
  - action.dispatch_backmerge        → none (in-handler per-repo cooldown
                                              in pr-remediator stays authoritative)

Re-enables the 6 actions disabled by PRs #436 and #451:
alert.fleet_agent_stuck, action.fleet_incident_response,
action.fleet_downshift_models, action.fleet_investigate_orphaned_skills,
action.issues_triage_critical, action.issues_triage_bugs.

Tests:
  - action-dispatcher unit tests cover: blocks repeats within window;
    A's cooldown does not affect B; window expiry admits next dispatch;
    absent cooldownMs and cooldownMs<=0 mean no throttling.
  - End-to-end test spam-publishes 100 violations of fleet.no_skill_orphaned
    and asserts exactly 1 dispatch reaches the executor.
  - bun test: 1023 / 1023 pass.




* fix(ceremonies): route 4 ceremonies to right skill owner, disable 3 broken (#454)

Audit of all 10 workspace/ceremonies/*.yaml against the live agent skill
registry surfaced 7 with skill-target mismatches that fail every fire:

ROUTED to correct skill owner:
  board.cleanup     skill=board_audit    targets [all] → [quinn]
  board.health      skill=board_health   targets [all] → [protomaker]
  daily-standup     skill=board_audit    targets [ava] → [quinn]
  health-check      skill=board_audit    targets [ava] → [quinn]

DISABLED (no agent advertises the skill):
  agent-health      skill=health_check   — no health_check anywhere
  board.retro       skill=pattern_analysis — no pattern_analysis anywhere
  service-health    skill=health_check   — same as agent-health

Quinn owns board_audit (and bug_triage / pr_review / qa_report).
Protomaker owns board_health (and 9 other apps/server skills).
The two `health_check`-keyed ceremonies were redundant anyway — the
agent_health and services world-state domains poll the same data every
60s and expose it on /api/agent-health and /api/services.

Re-enable the disabled three with a real skill if a periodic ANNOUNCE
to Discord is wanted (small `*_health_report` skill on protobot would
do it).

Workspace is bind-mounted, so the live container picks this up on
restart with no rebuild.




* feat(goap): pre-dispatch target registry guard (closes #444) (#455)

The GOAP planner could dispatch a skill to a target agent (e.g.
auto-triage-sweep, user) where the named target wasn't in the live
ExecutorRegistry. The dispatcher fired anyway, the executor errored
404-style, and the failure cascaded into stuck work items + duplicate
incident filings (INC-003 through INC-018, ~93 work items in error
state). The cooldown work in #437 / #452 masked the symptom but the
structural gap remained.

ActionDispatcherPlugin now takes the shared ExecutorRegistry handle and
runs `_admitOrTargetUnresolved` immediately after the cooldown check and
BEFORE the WIP queue / executor. Same chokepoint pattern as cooldown:

  - target absent (skill-routed)            → admit
  - target = "all" (broadcast sentinel)     → admit
  - target matches a registration agentName → admit
  - target unresolvable                     → drop, log loud, telemetry
                                              bump `target_unresolved`

Drops surface action id, the unresolvable target, AND the agents that
DO exist so the routing mistake is immediately diagnosable. The only
opt-out is the broadcast sentinel "all" — there is no flag, no
enabled-bool. Greenfield-strict shape.

Wiring: src/index.ts passes the shared executorRegistry into the
dispatcher factory. Test fixtures that don't exercise target routing
omit the registry and the check is skipped — so existing tests stay
green without modification.

Audit of workspace/actions.yaml: only `protomaker` appears as an active
agentId target (twice). That agent is registered in workspace/agents.yaml.
The historical bad targets (`auto-triage-sweep`, `user`) were removed by
prior cleanup; this PR ensures any future regression fails closed.

Tests added in src/plugins/action-dispatcher-plugin.test.ts:
  - admits when target is registered
  - drops when target is unregistered + bumps `target_unresolved`
  - drops on mixed-target intent (single-target shape today via meta.agentId)
  - admits when target is the "all" broadcast sentinel
  - admits when meta.agentId is absent (skill-routed dispatch)
  - admits when no registry is wired (legacy test fixtures)

bun test: 1029 / 1029 pass.




* fix(ceremony): honor enabled:false on initial load (closes #453) (#456)

PR #415 fixed the hot-reload path so flipping a ceremony to disabled
cancelled its timer, but the initial-load path still added every YAML
entry (disabled or not) to the in-memory registry. Two consequences:

  1. External `ceremony.<id>.execute` triggers (from
     CeremonySkillExecutorPlugin's GOAP bridge) found the disabled
     ceremony in the registry and fired it anyway.
  2. After hot-reload flipped a ceremony enabled→disabled, the entry
     stayed in the registry — same external-trigger leak.

Fix: filter `enabled === false` at every place that lands a ceremony in
the registry (initial install, hot-reload new-file path, hot-reload
changed-file path). Disabled ceremonies are loaded by the YAML parser
(so the changed-file path can detect a flip) but never reach the
registry, never schedule a timer, and cannot be resurrected by an
external trigger. Operators see a `Skipping disabled ceremony: <id>`
log line for each skip — fail-loud per project convention.

Greenfield: no flag, no toggle. enabled:false means disabled
everywhere.




* fix(tests): wrap subscribe-spy push callbacks to return void (#457)

13 TS2322 errors snuck through #452 and #455 because the CI test job
has been failing on type-check while build-and-push (the gate that
actually publishes :dev) is a separate workflow that runs on push.
Result: main/dev were green for container publish even though tsc
--noEmit was returning exit 2. Not visible in PR merge gates either
because test.conclusion=failure + build-and-push.conclusion=skipping
still resolved to a mergeable state.

Pattern of the 13 errors:

  bus.subscribe(T, "spy", (m) => requests.push(m));
                                  ^^^^^^^^^^^^^^^^
  Array.push() returns number, but the subscribe callback expects
  void | Promise<void>. Fix: wrap the body in a block so the arrow
  returns void:

  bus.subscribe(T, "spy", (m) => { requests.push(m); });

Applied via sed-style regex across both test files. 1029 tests still
pass (bun test). `bun run tsc --noEmit` now exits 0.




* fix(fleet-health): filter outcomes from synthetic actors via registry whitelist (closes #459) (#460)

AgentFleetHealthPlugin now takes an optional ExecutorRegistry (mirrors the
#455 wiring on ActionDispatcherPlugin). On each inbound autonomous.outcome,
`systemActor` is checked against `executorRegistry.list().map(r => r.agentName)`:

- Registered agent (ava, quinn, protomaker, ...) → aggregated in agents[]
  (existing shape, no behavior change).
- Anything else (pr-remediator, auto-triage-sweep, goap, user, ...) →
  routed to a separate systemActors[] bucket. No longer pollutes
  agentCount / maxFailureRate1h / orphanedSkillCount, so Ava's sitreps
  stop surfacing plugin names as "stuck agents".

First time a synthetic actor is seen, the plugin emits a one-time
console.warn naming the actor + skill (fail-fast and loud, per policy)
so operators know what's being filtered. No flag — same greenfield /
chokepoint discipline as #437 (cooldown) and #444 (target guard).

Scope note on `_default`: this plugin keys on outcome `systemActor`,
not registry `agentName`. `_default` only appears in /api/agent-health
(the registry-driven view). Nothing currently publishes an outcome with
`systemActor: "_default"`, so it doesn't reach agents[] here. If it
ever did, the new whitelist would drop it to systemActors[] — the right
outcome.

Verification plan (post-deploy):
  curl -s -X POST http://localhost:3000/v1/chat/completions \\
    -H 'Content-Type: application/json' \\
    -d '{"model":"ava","messages":[{"role":"user","content":"fleet sitrep"}]}'

Expected: no `pr-remediator`, `auto-triage-sweep`, or `user` in agents[].




* fix(a2a): agent card advertises canonical A2A endpoint, not the dashboard URL (#462)

The card at /.well-known/agent-card.json was advertising a URL like
http://ava:8081/a2a — host-mapped to the Astro dashboard, which 404s on
/a2a. Spec-compliant clients (@a2a-js/sdk and friends) doing card
discovery → POST to card.url could not reach the actual A2A endpoint;
the voice agent team papered over this by switching to the
/v1/chat/completions shim.

Fix: derive the card's `url` from variables that describe where the A2A
endpoint actually lives.

  1. WORKSTACEAN_PUBLIC_BASE_URL (e.g. https://ava.proto-labs.ai) →
     ${publicBase}/a2a. The canonical Cloudflare-fronted URL for
     external/Tailscale callers.
  2. Otherwise, http://${WORKSTACEAN_INTERNAL_HOST ?? "workstacean"}:${WORKSTACEAN_HTTP_PORT}/a2a
     — docker-network service name + the actual API port.

Also populate `additionalInterfaces` with the JSON-RPC transport at the
same URL so spec-compliant clients can pick deterministically. Drop the
WORKSTACEAN_BASE_URL coupling in the card builder — that variable
remains the externally-reachable URL stamped into A2A push-notification
callbacks (different concern, separate documentation).

Closes #461




---------

Co-authored-by: Josh Mabry <31560031+mabry1985@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Josh <artificialcitizens@gmail.com>
mabry1985 added a commit that referenced this pull request Apr 22, 2026
…ning (v0.7.23 candidate) (#475)

* fix: extension URIs use proto-labs.ai (not protolabs.ai) (#407)

All 27 references to https://protolabs.ai/a2a/ext/* changed to
https://proto-labs.ai/a2a/ext/* to match the actual domain. These
URIs are opaque identifiers (not published specs today) but should
reference a domain we own.

Breaking: external agents (Quinn, protoPen) whose cards declare the
old URI will stop matching the registry until they update. Filed on
Quinn to update her card.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(release): bump to v0.7.20 (#408)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: remove protoaudio + protovoice from agent rollcall (#410)

Both services decommissioned. Containers stopped + removed.
Only reference in protoWorkstacean was the rollcall script.

Note: homelab-iac/stacks/ai/docker-compose.yml still has a
worldmonitor network reference at line 521 + service at line 833.
Needs separate cleanup in that repo.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: upgrade web_search → searxng_search + give Ava fleet health tools (#411)

Two changes:

1. Replace the basic `web_search` tool (5 results, hardcoded engines)
   with `searxng_search` — adapted from rabbit-hole.io's full-surface
   SearXNG integration. New capabilities:
   - Category routing: general, news, science, it
   - Time range filtering: day, week, month, year
   - Bang syntax: !wp (Wikipedia), !scholar, !gh (GitHub)
   - Infoboxes, direct answers, suggestions in response
   - Configurable max_results (default 10, was 5)

   Updated in both bus-tools.ts (@protolabsai/sdk pattern) and
   deep-agent-executor.ts (LangChain pattern).

2. Give Ava three fleet health tools she was missing:
   - get_ci_health — CI success rates across repos
   - get_pr_pipeline — open PRs, conflicts, staleness
   - get_incidents — security/ops incidents

   Ava can now answer fleet health questions directly instead of
   always delegating to Quinn.

Ava's tool count: 10 → 13. Tool rename: web_search → searxng_search
(greenfield, no backward compat alias).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(projects): register protoAgent in projects.yaml (#414)

protoAgent is the new GitHub Template repo that replaces per-agent
A2A bootstrapping. Registers it as an active dev project owned by
Quinn, matching the shape of existing entries. Plane / GitHub
webhook / Discord provisioning remain TODO — those integrations
aren't configured in this deployment, so the onboard plugin
skipped them.

Co-authored-by: Josh <artificialcitizens@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(release): bump to v0.7.21 (#413)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(ava): expand helm toolset, wire GOAP skills, fix ceremony disable bug (#415)

Ava agent audit + overhaul:
- Tools: 10 → 22 (direct observation, propose_config_change, incident reporting)
- Skills: 3 → 7 (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills)
- System prompt rewritten: self-improvement instructions, escalation policy, GOAP-dispatch playbook
- DeepAgentExecutor now applies skill-level systemPromptOverride (goal_proposal, diagnose_pr_stuck)
- Fix ceremony loader bug: disabled ceremonies were filtered out, preventing hot-reload from cancelling timers
- Clean up board.pr-audit.yaml (remove spurious action field, restore schedule, keep disabled)
- Update docs: README, deep-agent runtime, agent-skills reference, self-improving loop

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): close dispatch gap — self-dispatch + broaden auto-approve (#417)

Two root causes prevented PRs from being auto-merged:

1. Dispatch gap: tier_0 short-circuit in ActionDispatcherPlugin completed
   all actions immediately without dispatching to agent.skill.request.
   Every action in actions.yaml is tier_0, so the fireAndForget path
   (which publishes the skill request) was unreachable dead code.
   Fix: tier_0 now falls through when meta.fireAndForget is set.

2. Approval gap: readyToMerge requires reviewState=approved, but
   auto-approve only covered dependabot/renovate/promote:/chore(deps.
   Human PRs, release PRs (chore(release), and github-actions PRs
   all lacked approved reviews and sat indefinitely.
   Fix: added app/github-actions to authors, chore(release, chore:,
   docs( to safe title prefixes.

Additionally, PrRemediatorPlugin now self-dispatches remediation on
every world.state.updated tick — checking for readyToMerge, dirty,
failingCi, and changesRequested PRs directly from cached domain data.
This removes the dependency on GOAP dispatch reaching the plugin via
pr.remediate.* topics (which were never published in production after
Arc 1.4 removed meta.topic routing).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(goap): issue_zero domain + goals — track open GitHub issues across fleet (#419)

Adds a github_issues domain that polls /repos/{repo}/issues?state=open
for all managed projects and classifies by label (critical, bug,
enhancement). Three GOAP goals enforce issue hygiene:

  - issues.zero_critical (critical severity, max: 0)
  - issues.zero_bugs (high severity, max: 0)
  - issues.total_low (medium severity, max: 5)

Each goal has a matching alert action and a triage dispatch action
that invokes Ava's new issue_triage skill. The skill instructs Ava
to resolve, convert to board features, delegate, or close issues
with rationale — driving toward zero open issues across all repos.

Domain polls every 5 minutes (issue velocity is low, GitHub rate
limits are a concern with 6+ repos).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: manage_board list action (#247) + a2a.trace extension (#359) (#420)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): case-insensitive auto-approve prefix matching (#421)

* feat: manage_board list action (#247) + a2a.trace extension (#359)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): case-insensitive auto-approve prefix matching

"Promote dev to main" titles start with capital P, but the prefix
check was case-sensitive against "promote:". Now lowercases the
title before matching so both "promote:" and "Promote" patterns
are caught.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(skill-dispatcher): wire alert.* executors + startup validator (#426) (#427)

Closes the structural gap where 6+ tier_0 fire-and-forget alert skills had
no registered executor, causing SkillDispatcherPlugin to log "No executor
found" and silently drop the dispatch on every GOAP planning cycle.

- AlertSkillExecutorPlugin registers FunctionExecutors for all 24 bare
  alert.* actions in workspace/actions.yaml. Each translates the dispatch
  into a structured message.outbound.discord.alert event consumed by the
  existing WorldEngineAlertPlugin webhook routing.
- validate-action-executors.ts cross-checks the loaded ActionRegistry
  against the live ExecutorRegistry at startup. Surfaces every gap as a
  HIGH-severity Discord alert (goal platform.skills_unwired) and a loud
  console.error. Set WORKSTACEAN_STRICT_WIRING=1 to crash startup instead.
- action.issues_triage_bugs already routes correctly via meta.agentId=ava
  to the existing DeepAgentExecutor for Ava's issue_triage skill — no
  duplicate wiring needed (greenfield).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ceremonies): stop world.state.# leak from ceremony snapshots (#428)

CeremonyStateExtension was publishing { domain, data } envelopes on
`world.state.snapshot` after every ceremony completion. GoalEvaluatorPlugin
subscribes to `world.state.#`, treated the malformed payload as a WorldState,
and emitted a "Selector ... not found" violation for every loaded goal on
every ceremony tick (the cluster of 25+ violations at each :15/:30 boundary
in the live container logs). All listed selectors (flow.efficiency.ratio,
services.discord.connected, agent_health.agentCount, etc.) actually exist
in the producer output — the goals are correct.

Changes:
- Move ceremony snapshot publish to `ceremony.state.snapshot` (off the
  world.state.# namespace). Leaves the existing CeremoniesState shape and
  consumers unchanged.
- Goal evaluator: defensive payload shape check. Reject single-domain
  envelopes ({ domain, data }) and other non-WorldState payloads loud-once
  instead of generating one violation per goal.
- Goal evaluator: startup selector validator. After the first valid world
  state arrives, walk every loaded goal's selector and HIGH-log any that
  doesn't resolve. Re-armed on goals.reload / config.reload so drift caught
  by future hot-reloads also surfaces.
- Tests: regression guard that CeremonyStateExtension does not publish on
  world.state.#; goal evaluator ignores malformed payloads; validator
  catches an intentionally broken selector.

Closes #424

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rollcall): point /rollcall skill at in-repo script (closes #425) (#429)

The Claude Code skill was calling the homelab-iac copy of agent-rollcall.sh,
which had drifted from this repo's copy. The in-repo script knows about
the in-process DeepAgent runtime (Ava, protoBot, Tuner) and the current
A2A fleet; the homelab-iac copy still probed for the archived ava-agent
container and the deprecated protoaudio/protovoice services.

Single source of truth: this repo. The homelab-iac copy was separately
synced in homelab-iac@64e8dcf.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Promote dev to main (v0.7.21) (#418) (#423)

* fix: extension URIs use proto-labs.ai (not protolabs.ai) (#407)

All 27 references to https://protolabs.ai/a2a/ext/* changed to
https://proto-labs.ai/a2a/ext/* to match the actual domain. These
URIs are opaque identifiers (not published specs today) but should
reference a domain we own.

Breaking: external agents (Quinn, protoPen) whose cards declare the
old URI will stop matching the registry until they update. Filed on
Quinn to update her card.




* chore(release): bump to v0.7.20 (#408)



* chore: remove protoaudio + protovoice from agent rollcall (#410)

Both services decommissioned. Containers stopped + removed.
Only reference in protoWorkstacean was the rollcall script.

Note: homelab-iac/stacks/ai/docker-compose.yml still has a
worldmonitor network reference at line 521 + service at line 833.
Needs separate cleanup in that repo.




* feat: upgrade web_search → searxng_search + give Ava fleet health tools (#411)

Two changes:

1. Replace the basic `web_search` tool (5 results, hardcoded engines)
   with `searxng_search` — adapted from rabbit-hole.io's full-surface
   SearXNG integration. New capabilities:
   - Category routing: general, news, science, it
   - Time range filtering: day, week, month, year
   - Bang syntax: !wp (Wikipedia), !scholar, !gh (GitHub)
   - Infoboxes, direct answers, suggestions in response
   - Configurable max_results (default 10, was 5)

   Updated in both bus-tools.ts (@protolabsai/sdk pattern) and
   deep-agent-executor.ts (LangChain pattern).

2. Give Ava three fleet health tools she was missing:
   - get_ci_health — CI success rates across repos
   - get_pr_pipeline — open PRs, conflicts, staleness
   - get_incidents — security/ops incidents

   Ava can now answer fleet health questions directly instead of
   always delegating to Quinn.

Ava's tool count: 10 → 13. Tool rename: web_search → searxng_search
(greenfield, no backward compat alias).




* chore(projects): register protoAgent in projects.yaml (#414)

protoAgent is the new GitHub Template repo that replaces per-agent
A2A bootstrapping. Registers it as an active dev project owned by
Quinn, matching the shape of existing entries. Plane / GitHub
webhook / Discord provisioning remain TODO — those integrations
aren't configured in this deployment, so the onboard plugin
skipped them.




* chore(release): bump to v0.7.21 (#413)



* feat(ava): expand helm toolset, wire GOAP skills, fix ceremony disable bug (#415)

Ava agent audit + overhaul:
- Tools: 10 → 22 (direct observation, propose_config_change, incident reporting)
- Skills: 3 → 7 (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills)
- System prompt rewritten: self-improvement instructions, escalation policy, GOAP-dispatch playbook
- DeepAgentExecutor now applies skill-level systemPromptOverride (goal_proposal, diagnose_pr_stuck)
- Fix ceremony loader bug: disabled ceremonies were filtered out, preventing hot-reload from cancelling timers
- Clean up board.pr-audit.yaml (remove spurious action field, restore schedule, keep disabled)
- Update docs: README, deep-agent runtime, agent-skills reference, self-improving loop




* fix(pr-remediator): close dispatch gap — self-dispatch + broaden auto-approve (#417)

Two root causes prevented PRs from being auto-merged:

1. Dispatch gap: tier_0 short-circuit in ActionDispatcherPlugin completed
   all actions immediately without dispatching to agent.skill.request.
   Every action in actions.yaml is tier_0, so the fireAndForget path
   (which publishes the skill request) was unreachable dead code.
   Fix: tier_0 now falls through when meta.fireAndForget is set.

2. Approval gap: readyToMerge requires reviewState=approved, but
   auto-approve only covered dependabot/renovate/promote:/chore(deps.
   Human PRs, release PRs (chore(release), and github-actions PRs
   all lacked approved reviews and sat indefinitely.
   Fix: added app/github-actions to authors, chore(release, chore:,
   docs( to safe title prefixes.

Additionally, PrRemediatorPlugin now self-dispatches remediation on
every world.state.updated tick — checking for readyToMerge, dirty,
failingCi, and changesRequested PRs directly from cached domain data.
This removes the dependency on GOAP dispatch reaching the plugin via
pr.remediate.* topics (which were never published in production after
Arc 1.4 removed meta.topic routing).




* feat(goap): issue_zero domain + goals — track open GitHub issues across fleet (#419)

Adds a github_issues domain that polls /repos/{repo}/issues?state=open
for all managed projects and classifies by label (critical, bug,
enhancement). Three GOAP goals enforce issue hygiene:

  - issues.zero_critical (critical severity, max: 0)
  - issues.zero_bugs (high severity, max: 0)
  - issues.total_low (medium severity, max: 5)

Each goal has a matching alert action and a triage dispatch action
that invokes Ava's new issue_triage skill. The skill instructs Ava
to resolve, convert to board features, delegate, or close issues
with rationale — driving toward zero open issues across all repos.

Domain polls every 5 minutes (issue velocity is low, GitHub rate
limits are a concern with 6+ repos).




* feat: manage_board list action (#247) + a2a.trace extension (#359) (#420)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.




* fix(pr-remediator): case-insensitive auto-approve prefix matching (#421)

* feat: manage_board list action (#247) + a2a.trace extension (#359)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.



* fix(pr-remediator): case-insensitive auto-approve prefix matching

"Promote dev to main" titles start with capital P, but the prefix
check was case-sensitive against "promote:". Now lowercases the
title before matching so both "promote:" and "Promote" patterns
are caught.



---------




---------

Co-authored-by: Josh Mabry <31560031+mabry1985@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Josh <artificialcitizens@gmail.com>

* feat(ceremony): wire ceremony.security_triage + ceremony.service_health_discord (#431)

Adds CeremonySkillExecutorPlugin — registers FunctionExecutors that bridge
GOAP `ceremony.*` actions to the matching `ceremony.<id>.execute` topic
CeremonyPlugin already listens for. Without this bridge,
SkillDispatcherPlugin dropped every dispatch with "No executor found …"
and (post-#427) emitted HIGH platform.skills_unwired alerts every cycle.

Mirrors the alert-skill-executor-plugin pattern from #427 — explicit
action→ceremony id mapping, install order matters (after registry,
before skill-dispatcher).

Partial fix for #430.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): wire 5 GOAP-dispatched skills + honor hitlPolicy (#432)

Closes the structural gap where 5 actions in workspace/actions.yaml route
to handlers in PrRemediatorPlugin but had no registered executor:
  - action.pr_update_branch    → pr.remediate.update_branch
  - action.pr_merge_ready      → pr.remediate.merge_ready
  - action.pr_fix_ci           → pr.remediate.fix_ci
  - action.pr_address_feedback → pr.remediate.address_feedback
  - action.dispatch_backmerge  → pr.backmerge.dispatch

Before this change SkillDispatcherPlugin logged "No executor found" and
dropped the dispatch every GOAP cycle. After PR #427's startup validator
the same gap raised platform.skills_unwired HIGH every tick.

Wiring follows the AlertSkillExecutorPlugin pattern from #427:
  - PrRemediatorSkillExecutorPlugin registers FunctionExecutors that
    publish on the existing pr-remediator subscription topics, keeping
    "bus is the contract" — no plugin holds a reference to the other.
  - Executors are fire-and-forget per actions.yaml meta. They return a
    successful SkillResult immediately; pr-remediator's handler runs
    asynchronously on the bus subscription.
  - Install order matches alert-skill-executor: AFTER ExecutorRegistry
    construction, BEFORE skill-dispatcher.

For action.pr_merge_ready specifically, the meta.hitlPolicy
(ttlMs: 1800000, onTimeout: approve) is now honoured. The executor
forwards meta into the trigger payload; _handleMergeReady extracts it
via _extractHitlPolicy and passes it to _emitHitlApproval, which
populates HITLRequest.{ttlMs, onTimeout}. HITLPlugin already auto-
publishes a synthetic approve response when onTimeout=approve fires.

Closes part of #430. Ceremony + protoMaker actions ship in a separate PR.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(agents): register protomaker A2A agent (closes part of #430) (#433)

protoMaker (apps/server in protoLabsAI/ava) has been A2A-ready for a while —
serves agent-card.json with 10 skills including the two referenced by
unwired GOAP actions:
  - action.protomaker_triage_blocked → skill board_health
  - action.protomaker_start_auto_mode → skill auto_mode

Both actions targeted [protomaker], but no agent named "protomaker" was
registered, so the dispatcher couldn't route. Adding the entry closes
the routing gap; A2AExecutor's existing target-matching does the rest.

Endpoint: http://automaker-server:3008/a2a (verified from inside the
workstacean container with AVA_API_KEY → JSON-RPC 2.0 response).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(agents): drop overscoped subscribesTo from protomaker entry (#434)

#433 added `subscribesTo: message.inbound.github.#` to the new protomaker
agent registration, copy-pasted from quinn's pattern. That was wrong:
protoMaker is reached via explicit GOAP `targets: [protomaker]` dispatches
(action.protomaker_triage_blocked, action.protomaker_start_auto_mode), not
as a broadcast inbound listener.

Quinn already subscribes to all GitHub inbound and dispatches `bug_triage`
on protoMaker's behalf. Having protomaker subscribe to the same broadcast
topic is one of the contributing paths to the duplicate-triage spam loop
filed as protoLabsAI/protoMaker#3503 (the root cause is Quinn's handler
not being idempotent — but this cleanup removes one extra firing path).

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ava): rename web_search → searxng_search to match runtime tool name (#435)

PR #411 renamed the tool from web_search to searxng_search in
src/agent-runtime/tools/bus-tools.ts (line 393), but ava.yaml still
declared the old name. Result: at startup the runtime warns
"agent ava declares unknown tools: web_search" and Ava ends up with no
search capability — when asked, she explicitly responds "I don't have a
searxng_search or web_search tool in my current toolkit."

This is the config side of the half-finished rename that PR #411 missed.
After this lands and workstacean restarts, Ava's toolkit should include
searxng_search and the unknown-tools warning should be empty for her.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(goap): disable fleet_agent_stuck loop — fires every cycle, no dedup (#436)

Two GOAP actions on goal fleet.no_agent_stuck were spamming Discord
because they had no `effects` and no cooldown:

  - alert.fleet_agent_stuck → posts a Discord alert
  - action.fleet_incident_response → dispatches Ava to file an incident,
    page on-call, and pause routing

Observed 2026-04-20: when auto-triage-sweep hit 100% failure rate on
bug_triage (cascading from the non-idempotent handler in
protoLabsAI/protoMaker#3503), GOAP re-fired both actions every planning
cycle. Ava filed INC-003 through INC-009 in ~30 seconds, each posting to
Discord. The pause routing succeeded but the rolling 1h failure rate
metric doesn't drop instantly, so the goal stayed violated and the loop
kept re-firing.

Disabling both actions until proper dedup lands. Reinstate when:
  1. action.fleet_incident_response gains a cooldown OR an `effects`
     marker that satisfies the goal for the cooldown window
  2. Ava's fleet_incident_response skill checks for an existing open
     incident on the same agent before filing a new one
  3. alert.fleet_agent_stuck gains per-agent rate limiting

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(goap): disable 4 more action.* loops — same no-cooldown bug as #436 (#451)

After #436 disabled action.fleet_incident_response, observed similar
loop spam from FOUR more action.* dispatches that share the same
architectural bug (no cooldown, no satisfying effects → re-fire every
GOAP cycle while goal stays violated):

  action.fleet_investigate_orphaned_skills  — Ava posts orphaned-skill
                                               diagnosis to Discord ops
                                               on every cycle (8+ posts
                                               in 1 min observed)
  action.issues_triage_critical             — ~447 fires in 30 min
  action.issues_triage_bugs                 — ~447 fires in 30 min;
                                               compounds with #3503
                                               by re-triaging same issues
  action.fleet_downshift_models             — same pattern when cost
                                               exceeds budget

ALL share the same pattern as the alert.* actions and the original
fleet_incident_response: effects: [] + no cooldownMs + persistent goal
violation = infinite re-fire.

Mitigation only — Ava temporarily can't auto-investigate orphaned
skills or auto-triage new GitHub issues. Reinstate when issue #437
ships action-level cooldown (meta.cooldownMs) or proper effects-with-TTL.
The alert.* siblings (24 of them) also have this bug but are
non-impacting today because DISCORD_WEBHOOK_ALERTS is unset and
WorldEngineAlertPlugin drops them silently — fixing #437 will cover
both at the dispatcher level.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(goap): per-action cooldown in ActionDispatcher (closes #437) (#452)

GOAP actions with `effects: []` re-fired every planning cycle (~3s) while
their goal stayed violated. Two prod fires (PR #436, PR #451) had to
disable 6 actions before this lands. Restoring autonomy.

ActionDispatcherPlugin now honors `meta.cooldownMs` on every action. When
an action with a positive cooldownMs fires, the dispatcher records its
timestamp; subsequent dispatches of the same action id within the window
are dropped BEFORE the WIP queue and BEFORE the executor. Single chokepoint
covers both alert.* (FunctionExecutor → Discord) and action.* (DeepAgent /
A2A) paths. Drops log a fail-fast diagnostic with action id, age, and
remaining window, plus bump a new `cooldown_dropped` telemetry event.

Cooldown bucket is keyed on action id alone — per-target keying isn't
needed because each GOAP action targets one situation. Greenfield shape:
absence of `meta.cooldownMs` means "no cooldown" naturally, no flag.

Defaults applied to workspace/actions.yaml:
  - alert.*                          → 15 min
  - action.* with skillHint          → 30 min (agent work is expensive)
  - action.pr_*                      → 5 min  (remediation must stay responsive)
  - ceremony.*                       → 30 min (treated as skill dispatch)
  - action.dispatch_backmerge        → none (in-handler per-repo cooldown
                                              in pr-remediator stays authoritative)

Re-enables the 6 actions disabled by PRs #436 and #451:
alert.fleet_agent_stuck, action.fleet_incident_response,
action.fleet_downshift_models, action.fleet_investigate_orphaned_skills,
action.issues_triage_critical, action.issues_triage_bugs.

Tests:
  - action-dispatcher unit tests cover: blocks repeats within window;
    A's cooldown does not affect B; window expiry admits next dispatch;
    absent cooldownMs and cooldownMs<=0 mean no throttling.
  - End-to-end test spam-publishes 100 violations of fleet.no_skill_orphaned
    and asserts exactly 1 dispatch reaches the executor.
  - bun test: 1023 / 1023 pass.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ceremonies): route 4 ceremonies to right skill owner, disable 3 broken (#454)

Audit of all 10 workspace/ceremonies/*.yaml against the live agent skill
registry surfaced 7 with skill-target mismatches that fail every fire:

ROUTED to correct skill owner:
  board.cleanup     skill=board_audit    targets [all] → [quinn]
  board.health      skill=board_health   targets [all] → [protomaker]
  daily-standup     skill=board_audit    targets [ava] → [quinn]
  health-check      skill=board_audit    targets [ava] → [quinn]

DISABLED (no agent advertises the skill):
  agent-health      skill=health_check   — no health_check anywhere
  board.retro       skill=pattern_analysis — no pattern_analysis anywhere
  service-health    skill=health_check   — same as agent-health

Quinn owns board_audit (and bug_triage / pr_review / qa_report).
Protomaker owns board_health (and 9 other apps/server skills).
The two `health_check`-keyed ceremonies were redundant anyway — the
agent_health and services world-state domains poll the same data every
60s and expose it on /api/agent-health and /api/services.

Re-enable the disabled three with a real skill if a periodic ANNOUNCE
to Discord is wanted (small `*_health_report` skill on protobot would
do it).

Workspace is bind-mounted, so the live container picks this up on
restart with no rebuild.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(goap): pre-dispatch target registry guard (closes #444) (#455)

The GOAP planner could dispatch a skill to a target agent (e.g.
auto-triage-sweep, user) where the named target wasn't in the live
ExecutorRegistry. The dispatcher fired anyway, the executor errored
404-style, and the failure cascaded into stuck work items + duplicate
incident filings (INC-003 through INC-018, ~93 work items in error
state). The cooldown work in #437 / #452 masked the symptom but the
structural gap remained.

ActionDispatcherPlugin now takes the shared ExecutorRegistry handle and
runs `_admitOrTargetUnresolved` immediately after the cooldown check and
BEFORE the WIP queue / executor. Same chokepoint pattern as cooldown:

  - target absent (skill-routed)            → admit
  - target = "all" (broadcast sentinel)     → admit
  - target matches a registration agentName → admit
  - target unresolvable                     → drop, log loud, telemetry
                                              bump `target_unresolved`

Drops surface action id, the unresolvable target, AND the agents that
DO exist so the routing mistake is immediately diagnosable. The only
opt-out is the broadcast sentinel "all" — there is no flag, no
enabled-bool. Greenfield-strict shape.

Wiring: src/index.ts passes the shared executorRegistry into the
dispatcher factory. Test fixtures that don't exercise target routing
omit the registry and the check is skipped — so existing tests stay
green without modification.

Audit of workspace/actions.yaml: only `protomaker` appears as an active
agentId target (twice). That agent is registered in workspace/agents.yaml.
The historical bad targets (`auto-triage-sweep`, `user`) were removed by
prior cleanup; this PR ensures any future regression fails closed.

Tests added in src/plugins/action-dispatcher-plugin.test.ts:
  - admits when target is registered
  - drops when target is unregistered + bumps `target_unresolved`
  - drops on mixed-target intent (single-target shape today via meta.agentId)
  - admits when target is the "all" broadcast sentinel
  - admits when meta.agentId is absent (skill-routed dispatch)
  - admits when no registry is wired (legacy test fixtures)

bun test: 1029 / 1029 pass.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ceremony): honor enabled:false on initial load (closes #453) (#456)

PR #415 fixed the hot-reload path so flipping a ceremony to disabled
cancelled its timer, but the initial-load path still added every YAML
entry (disabled or not) to the in-memory registry. Two consequences:

  1. External `ceremony.<id>.execute` triggers (from
     CeremonySkillExecutorPlugin's GOAP bridge) found the disabled
     ceremony in the registry and fired it anyway.
  2. After hot-reload flipped a ceremony enabled→disabled, the entry
     stayed in the registry — same external-trigger leak.

Fix: filter `enabled === false` at every place that lands a ceremony in
the registry (initial install, hot-reload new-file path, hot-reload
changed-file path). Disabled ceremonies are loaded by the YAML parser
(so the changed-file path can detect a flip) but never reach the
registry, never schedule a timer, and cannot be resurrected by an
external trigger. Operators see a `Skipping disabled ceremony: <id>`
log line for each skip — fail-loud per project convention.

Greenfield: no flag, no toggle. enabled:false means disabled
everywhere.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(tests): wrap subscribe-spy push callbacks to return void (#457)

13 TS2322 errors snuck through #452 and #455 because the CI test job
has been failing on type-check while build-and-push (the gate that
actually publishes :dev) is a separate workflow that runs on push.
Result: main/dev were green for container publish even though tsc
--noEmit was returning exit 2. Not visible in PR merge gates either
because test.conclusion=failure + build-and-push.conclusion=skipping
still resolved to a mergeable state.

Pattern of the 13 errors:

  bus.subscribe(T, "spy", (m) => requests.push(m));
                                  ^^^^^^^^^^^^^^^^
  Array.push() returns number, but the subscribe callback expects
  void | Promise<void>. Fix: wrap the body in a block so the arrow
  returns void:

  bus.subscribe(T, "spy", (m) => { requests.push(m); });

Applied via sed-style regex across both test files. 1029 tests still
pass (bun test). `bun run tsc --noEmit` now exits 0.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(fleet-health): filter outcomes from synthetic actors via registry whitelist (closes #459) (#460)

AgentFleetHealthPlugin now takes an optional ExecutorRegistry (mirrors the
#455 wiring on ActionDispatcherPlugin). On each inbound autonomous.outcome,
`systemActor` is checked against `executorRegistry.list().map(r => r.agentName)`:

- Registered agent (ava, quinn, protomaker, ...) → aggregated in agents[]
  (existing shape, no behavior change).
- Anything else (pr-remediator, auto-triage-sweep, goap, user, ...) →
  routed to a separate systemActors[] bucket. No longer pollutes
  agentCount / maxFailureRate1h / orphanedSkillCount, so Ava's sitreps
  stop surfacing plugin names as "stuck agents".

First time a synthetic actor is seen, the plugin emits a one-time
console.warn naming the actor + skill (fail-fast and loud, per policy)
so operators know what's being filtered. No flag — same greenfield /
chokepoint discipline as #437 (cooldown) and #444 (target guard).

Scope note on `_default`: this plugin keys on outcome `systemActor`,
not registry `agentName`. `_default` only appears in /api/agent-health
(the registry-driven view). Nothing currently publishes an outcome with
`systemActor: "_default"`, so it doesn't reach agents[] here. If it
ever did, the new whitelist would drop it to systemActors[] — the right
outcome.

Verification plan (post-deploy):
  curl -s -X POST http://localhost:3000/v1/chat/completions \\
    -H 'Content-Type: application/json' \\
    -d '{"model":"ava","messages":[{"role":"user","content":"fleet sitrep"}]}'

Expected: no `pr-remediator`, `auto-triage-sweep`, or `user` in agents[].

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(a2a): agent card advertises canonical A2A endpoint, not the dashboard URL (#462)

The card at /.well-known/agent-card.json was advertising a URL like
http://ava:8081/a2a — host-mapped to the Astro dashboard, which 404s on
/a2a. Spec-compliant clients (@a2a-js/sdk and friends) doing card
discovery → POST to card.url could not reach the actual A2A endpoint;
the voice agent team papered over this by switching to the
/v1/chat/completions shim.

Fix: derive the card's `url` from variables that describe where the A2A
endpoint actually lives.

  1. WORKSTACEAN_PUBLIC_BASE_URL (e.g. https://ava.proto-labs.ai) →
     ${publicBase}/a2a. The canonical Cloudflare-fronted URL for
     external/Tailscale callers.
  2. Otherwise, http://${WORKSTACEAN_INTERNAL_HOST ?? "workstacean"}:${WORKSTACEAN_HTTP_PORT}/a2a
     — docker-network service name + the actual API port.

Also populate `additionalInterfaces` with the JSON-RPC transport at the
same URL so spec-compliant clients can pick deterministically. Drop the
WORKSTACEAN_BASE_URL coupling in the card builder — that variable
remains the externally-reachable URL stamped into A2A push-notification
callbacks (different concern, separate documentation).

Closes #461

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): never auto-close promotion PRs on decomposable verdict (closes #465) (#469)

Two-layer fix per the issue body:

Layer A — Ava's diagnose_pr_stuck prompt (workspace/agents/ava.yaml):
The promotion-PR rule is now stated FIRST, above the four verdict
definitions. When the head is dev/staging OR base is main/staging OR
title starts "Promote"/"promote:", the verdict is always rebasable —
drift between branches is fixed with a back-merge of base into head,
not by splitting the PR. Phrased in positive framing per the
no-negative-reinforcement memory.

Layer B — code guard (lib/plugins/pr-remediator.ts):
New isPromotionPr() helper, called at the case "decomposable" handler
chokepoint before the close+comment path. On promotion PRs the guard
warns loudly (naming head/base/title), escalates to HITL via the
existing _emitStuckHitlEscalation pathway, and returns. PrDomainEntry
gains an optional headRef field; src/api/github.ts surfaces pr.head.ref
in the pr_pipeline domain so the guard can see it. _dispatchDiagnose
also adds "Head branch:" to the prompt payload so Ava sees the same
field.

Same defense-in-depth shape as #437 (cooldown), #444 (target registry),
#459 (synthetic actor filter): invariants live at the action-site
chokepoint, not at the planner. A drifting prompt cannot close a
release-pipeline PR.

Tests: 4 new cases in src/pr-remediator.test.ts cover refactor PR
(still closes), dev→main / staging→main / dev→staging promotion PRs
(escalate to HITL, do NOT close). Full suite 1047/1047 pass.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr-remediator): Number.isFinite guard on ttlMs — Infinity/NaN RangeError (#467 finding #2) (#470)

* fix(pr-remediator): guard ttlMs against Infinity/NaN with Number.isFinite

_extractHitlPolicy accepted any typeof "number" for ttlMs, which includes
Infinity and NaN. Both pass the typeof check but cause
new Date(Date.now() + ttlMs).toISOString() to throw a RangeError.

Fix: add Number.isFinite(p.ttlMs) && p.ttlMs > 0 guard.

Fixes finding #2 from GitHub issue #467 (CodeRabbit review on PR #466).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(plugins): gate pr-remediator-skill-executor with GitHub credential condition

`pr-remediator-skill-executor` was unconditionally installed (condition: () => true),
but `pr-remediator` itself is gated on GitHub credentials. When creds are absent,
dispatches to `pr.remediate.*` topics passed validation and the executor ran — but
no subscriber consumed them, resulting in silent success with no actual work done.

Fix: apply the same condition guard used by `pr-remediator`:
  !!(process.env.QUINN_APP_PRIVATE_KEY || process.env.GITHUB_TOKEN)

Resolves GitHub issue #467 (CodeRabbit finding #4 from PR #466).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(a2a-server): default to [ava] target + sanitize upstream HTML errors (closes #471) (#473)

Two bugs reported by protoVoice against ava.proto-labs.ai/a2a:

1. message/send with no metadata routed to protoBot (the router's
   default chat agent) instead of Ava. This endpoint is Ava's — the
   orchestrator card aggregates the fleet's skills, but routing defaults
   here must be Ava. Callers targeting another agent pass explicit
   metadata.targets. Logs an info line on the default path so operators
   can see the fallback firing.

2. When a downstream A2A sub-call was misrouted (e.g. upstream
   protoLabsAI/protoMaker#3536 — broken card URL), the raw HTML 404 page
   bubbled through the bus response's content field and was rendered as
   the assistant's reply text. BusAgentExecutor now detects HTML-looking
   payloads (<!DOCTYPE, <html, Cannot POST/GET, 404 Not Found), treats
   them as failures, logs the raw payload at warn level for debugging,
   and replaces the final message text with a sanitized operator-facing
   string that includes a short stripped debug hint.

No flag, no opt-out — greenfield, new behavior is the only behavior.

Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(467): clear remaining hardening items from CodeRabbit PR #466 review

Closes the three open CodeRabbit findings from #466 not yet shipped
(items #2 and #4 already landed via #470 and 9792b0c).

#467 finding #1 (.claude/commands/rollcall.md):
  Hard-coded operator path /home/josh/dev/... → relative path
  scripts/agent-rollcall.sh with explicit "from repo root" guidance.
  Works for any clone; matches every other repo-script reference.

#467 finding #3 (README.md env table):
  Add a dedicated env-table row for WORKSTACEAN_INTERNAL_HOST so it's
  discoverable by anyone overriding the docker-network default.
  Cross-references WORKSTACEAN_PUBLIC_BASE_URL.

#467 finding #5 (src/index.ts startup-validator):
  validateActionExecutors() ran BEFORE loadWorkspacePlugins(), so
  executor registrars shipped as workspace plugins were falsely flagged
  in strict mode. It also ran only once at startup, so config.reload of
  actions.yaml bypassed the fail-loud guard.

  Fix: extract a runWiringValidator(reason) helper, call it AFTER all
  plugin loading (core + registered + workspace), and re-run inside the
  CONFIG_RELOAD subscriber after loadActionsYaml(). New "[reload-validator]"
  log tag distinguishes the call sites.

Tests: 1054/1054 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Promote dev → main: agent card + fleet-health filter + ceremony fixes (v0.7.22 candidate, re-cut) (#466) (#468)

* fix: extension URIs use proto-labs.ai (not protolabs.ai) (#407)

All 27 references to https://protolabs.ai/a2a/ext/* changed to
https://proto-labs.ai/a2a/ext/* to match the actual domain. These
URIs are opaque identifiers (not published specs today) but should
reference a domain we own.

Breaking: external agents (Quinn, protoPen) whose cards declare the
old URI will stop matching the registry until they update. Filed on
Quinn to update her card.




* chore(release): bump to v0.7.20 (#408)



* chore: remove protoaudio + protovoice from agent rollcall (#410)

Both services decommissioned. Containers stopped + removed.
Only reference in protoWorkstacean was the rollcall script.

Note: homelab-iac/stacks/ai/docker-compose.yml still has a
worldmonitor network reference at line 521 + service at line 833.
Needs separate cleanup in that repo.




* feat: upgrade web_search → searxng_search + give Ava fleet health tools (#411)

Two changes:

1. Replace the basic `web_search` tool (5 results, hardcoded engines)
   with `searxng_search` — adapted from rabbit-hole.io's full-surface
   SearXNG integration. New capabilities:
   - Category routing: general, news, science, it
   - Time range filtering: day, week, month, year
   - Bang syntax: !wp (Wikipedia), !scholar, !gh (GitHub)
   - Infoboxes, direct answers, suggestions in response
   - Configurable max_results (default 10, was 5)

   Updated in both bus-tools.ts (@protolabsai/sdk pattern) and
   deep-agent-executor.ts (LangChain pattern).

2. Give Ava three fleet health tools she was missing:
   - get_ci_health — CI success rates across repos
   - get_pr_pipeline — open PRs, conflicts, staleness
   - get_incidents — security/ops incidents

   Ava can now answer fleet health questions directly instead of
   always delegating to Quinn.

Ava's tool count: 10 → 13. Tool rename: web_search → searxng_search
(greenfield, no backward compat alias).




* chore(projects): register protoAgent in projects.yaml (#414)

protoAgent is the new GitHub Template repo that replaces per-agent
A2A bootstrapping. Registers it as an active dev project owned by
Quinn, matching the shape of existing entries. Plane / GitHub
webhook / Discord provisioning remain TODO — those integrations
aren't configured in this deployment, so the onboard plugin
skipped them.




* chore(release): bump to v0.7.21 (#413)



* feat(ava): expand helm toolset, wire GOAP skills, fix ceremony disable bug (#415)

Ava agent audit + overhaul:
- Tools: 10 → 22 (direct observation, propose_config_change, incident reporting)
- Skills: 3 → 7 (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills)
- System prompt rewritten: self-improvement instructions, escalation policy, GOAP-dispatch playbook
- DeepAgentExecutor now applies skill-level systemPromptOverride (goal_proposal, diagnose_pr_stuck)
- Fix ceremony loader bug: disabled ceremonies were filtered out, preventing hot-reload from cancelling timers
- Clean up board.pr-audit.yaml (remove spurious action field, restore schedule, keep disabled)
- Update docs: README, deep-agent runtime, agent-skills reference, self-improving loop




* fix(pr-remediator): close dispatch gap — self-dispatch + broaden auto-approve (#417)

Two root causes prevented PRs from being auto-merged:

1. Dispatch gap: tier_0 short-circuit in ActionDispatcherPlugin completed
   all actions immediately without dispatching to agent.skill.request.
   Every action in actions.yaml is tier_0, so the fireAndForget path
   (which publishes the skill request) was unreachable dead code.
   Fix: tier_0 now falls through when meta.fireAndForget is set.

2. Approval gap: readyToMerge requires reviewState=approved, but
   auto-approve only covered dependabot/renovate/promote:/chore(deps.
   Human PRs, release PRs (chore(release), and github-actions PRs
   all lacked approved reviews and sat indefinitely.
   Fix: added app/github-actions to authors, chore(release, chore:,
   docs( to safe title prefixes.

Additionally, PrRemediatorPlugin now self-dispatches remediation on
every world.state.updated tick — checking for readyToMerge, dirty,
failingCi, and changesRequested PRs directly from cached domain data.
This removes the dependency on GOAP dispatch reaching the plugin via
pr.remediate.* topics (which were never published in production after
Arc 1.4 removed meta.topic routing).




* feat(goap): issue_zero domain + goals — track open GitHub issues across fleet (#419)

Adds a github_issues domain that polls /repos/{repo}/issues?state=open
for all managed projects and classifies by label (critical, bug,
enhancement). Three GOAP goals enforce issue hygiene:

  - issues.zero_critical (critical severity, max: 0)
  - issues.zero_bugs (high severity, max: 0)
  - issues.total_low (medium severity, max: 5)

Each goal has a matching alert action and a triage dispatch action
that invokes Ava's new issue_triage skill. The skill instructs Ava
to resolve, convert to board features, delegate, or close issues
with rationale — driving toward zero open issues across all repos.

Domain polls every 5 minutes (issue velocity is low, GitHub rate
limits are a concern with 6+ repos).




* feat: manage_board list action (#247) + a2a.trace extension (#359) (#420)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.




* fix(pr-remediator): case-insensitive auto-approve prefix matching (#421)

* feat: manage_board list action (#247) + a2a.trace extension (#359)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.



* fix(pr-remediator): case-insensitive auto-approve prefix matching

"Promote dev to main" titles start with capital P, but the prefix
check was case-sensitive against "promote:". Now lowercases the
title before matching so both "promote:" and "Promote" patterns
are caught.



---------




* fix(skill-dispatcher): wire alert.* executors + startup validator (#426) (#427)

Closes the structural gap where 6+ tier_0 fire-and-forget alert skills had
no registered executor, causing SkillDispatcherPlugin to log "No executor
found" and silently drop the dispatch on every GOAP planning cycle.

- AlertSkillExecutorPlugin registers FunctionExecutors for all 24 bare
  alert.* actions in workspace/actions.yaml. Each translates the dispatch
  into a structured message.outbound.discord.alert event consumed by the
  existing WorldEngineAlertPlugin webhook routing.
- validate-action-executors.ts cross-checks the loaded ActionRegistry
  against the live ExecutorRegistry at startup. Surfaces every gap as a
  HIGH-severity Discord alert (goal platform.skills_unwired) and a loud
  console.error. Set WORKSTACEAN_STRICT_WIRING=1 to crash startup instead.
- action.issues_triage_bugs already routes correctly via meta.agentId=ava
  to the existing DeepAgentExecutor for Ava's issue_triage skill — no
  duplicate wiring needed (greenfield).




* fix(ceremonies): stop world.state.# leak from ceremony snapshots (#428)

CeremonyStateExtension was publishing { domain, data } envelopes on
`world.state.snapshot` after every ceremony completion. GoalEvaluatorPlugin
subscribes to `world.state.#`, treated the malformed payload as a WorldState,
and emitted a "Selector ... not found" violation for every loaded goal on
every ceremony tick (the cluster of 25+ violations at each :15/:30 boundary
in the live container logs). All listed selectors (flow.efficiency.ratio,
services.discord.connected, agent_health.agentCount, etc.) actually exist
in the producer output — the goals are correct.

Changes:
- Move ceremony snapshot publish to `ceremony.state.snapshot` (off the
  world.state.# namespace). Leaves the existing CeremoniesState shape and
  consumers unchanged.
- Goal evaluator: defensive payload shape check. Reject single-domain
  envelopes ({ domain, data }) and other non-WorldState payloads loud-once
  instead of generating one violation per goal.
- Goal evaluator: startup selector validator. After the first valid world
  state arrives, walk every loaded goal's selector and HIGH-log any that
  doesn't resolve. Re-armed on goals.reload / config.reload so drift caught
  by future hot-reloads also surfaces.
- Tests: regression guard that CeremonyStateExtension does not publish on
  world.state.#; goal evaluator ignores malformed payloads; validator
  catches an intentionally broken selector.

Closes #424




* fix(rollcall): point /rollcall skill at in-repo script (closes #425) (#429)

The Claude Code skill was calling the homelab-iac copy of agent-rollcall.sh,
which had drifted from this repo's copy. The in-repo script knows about
the in-process DeepAgent runtime (Ava, protoBot, Tuner) and the current
A2A fleet; the homelab-iac copy still probed for the archived ava-agent
container and the deprecated protoaudio/protovoice services.

Single source of truth: this repo. The homelab-iac copy was separately
synced in homelab-iac@64e8dcf.




* Promote dev to main (v0.7.21) (#418) (#423)

* fix: extension URIs use proto-labs.ai (not protolabs.ai) (#407)

All 27 references to https://protolabs.ai/a2a/ext/* changed to
https://proto-labs.ai/a2a/ext/* to match the actual domain. These
URIs are opaque identifiers (not published specs today) but should
reference a domain we own.

Breaking: external agents (Quinn, protoPen) whose cards declare the
old URI will stop matching the registry until they update. Filed on
Quinn to update her card.




* chore(release): bump to v0.7.20 (#408)



* chore: remove protoaudio + protovoice from agent rollcall (#410)

Both services decommissioned. Containers stopped + removed.
Only reference in protoWorkstacean was the rollcall script.

Note: homelab-iac/stacks/ai/docker-compose.yml still has a
worldmonitor network reference at line 521 + service at line 833.
Needs separate cleanup in that repo.




* feat: upgrade web_search → searxng_search + give Ava fleet health tools (#411)

Two changes:

1. Replace the basic `web_search` tool (5 results, hardcoded engines)
   with `searxng_search` — adapted from rabbit-hole.io's full-surface
   SearXNG integration. New capabilities:
   - Category routing: general, news, science, it
   - Time range filtering: day, week, month, year
   - Bang syntax: !wp (Wikipedia), !scholar, !gh (GitHub)
   - Infoboxes, direct answers, suggestions in response
   - Configurable max_results (default 10, was 5)

   Updated in both bus-tools.ts (@protolabsai/sdk pattern) and
   deep-agent-executor.ts (LangChain pattern).

2. Give Ava three fleet health tools she was missing:
   - get_ci_health — CI success rates across repos
   - get_pr_pipeline — open PRs, conflicts, staleness
   - get_incidents — security/ops incidents

   Ava can now answer fleet health questions directly instead of
   always delegating to Quinn.

Ava's tool count: 10 → 13. Tool rename: web_search → searxng_search
(greenfield, no backward compat alias).




* chore(projects): register protoAgent in projects.yaml (#414)

protoAgent is the new GitHub Template repo that replaces per-agent
A2A bootstrapping. Registers it as an active dev project owned by
Quinn, matching the shape of existing entries. Plane / GitHub
webhook / Discord provisioning remain TODO — those integrations
aren't configured in this deployment, so the onboard plugin
skipped them.




* chore(release): bump to v0.7.21 (#413)



* feat(ava): expand helm toolset, wire GOAP skills, fix ceremony disable bug (#415)

Ava agent audit + overhaul:
- Tools: 10 → 22 (direct observation, propose_config_change, incident reporting)
- Skills: 3 → 7 (debug_ci_failures, fleet_incident_response, downshift_models, investigate_orphaned_skills)
- System prompt rewritten: self-improvement instructions, escalation policy, GOAP-dispatch playbook
- DeepAgentExecutor now applies skill-level systemPromptOverride (goal_proposal, diagnose_pr_stuck)
- Fix ceremony loader bug: disabled ceremonies were filtered out, preventing hot-reload from cancelling timers
- Clean up board.pr-audit.yaml (remove spurious action field, restore schedule, keep disabled)
- Update docs: README, deep-agent runtime, agent-skills reference, self-improving loop




* fix(pr-remediator): close dispatch gap — self-dispatch + broaden auto-approve (#417)

Two root causes prevented PRs from being auto-merged:

1. Dispatch gap: tier_0 short-circuit in ActionDispatcherPlugin completed
   all actions immediately without dispatching to agent.skill.request.
   Every action in actions.yaml is tier_0, so the fireAndForget path
   (which publishes the skill request) was unreachable dead code.
   Fix: tier_0 now falls through when meta.fireAndForget is set.

2. Approval gap: readyToMerge requires reviewState=approved, but
   auto-approve only covered dependabot/renovate/promote:/chore(deps.
   Human PRs, release PRs (chore(release), and github-actions PRs
   all lacked approved reviews and sat indefinitely.
   Fix: added app/github-actions to authors, chore(release, chore:,
   docs( to safe title prefixes.

Additionally, PrRemediatorPlugin now self-dispatches remediation on
every world.state.updated tick — checking for readyToMerge, dirty,
failingCi, and changesRequested PRs directly from cached domain data.
This removes the dependency on GOAP dispatch reaching the plugin via
pr.remediate.* topics (which were never published in production after
Arc 1.4 removed meta.topic routing).




* feat(goap): issue_zero domain + goals — track open GitHub issues across fleet (#419)

Adds a github_issues domain that polls /repos/{repo}/issues?state=open
for all managed projects and classifies by label (critical, bug,
enhancement). Three GOAP goals enforce issue hygiene:

  - issues.zero_critical (critical severity, max: 0)
  - issues.zero_bugs (high severity, max: 0)
  - issues.total_low (medium severity, max: 5)

Each goal has a matching alert action and a triage dispatch action
that invokes Ava's new issue_triage skill. The skill instructs Ava
to resolve, convert to board features, delegate, or close issues
with rationale — driving toward zero open issues across all repos.

Domain polls every 5 minutes (issue velocity is low, GitHub rate
limits are a concern with 6+ repos).




* feat: manage_board list action (#247) + a2a.trace extension (#359) (#420)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.




* fix(pr-remediator): case-insensitive auto-approve prefix matching (#421)

* feat: manage_board list action (#247) + a2a.trace extension (#359)

Two enhancements to reach issue zero:

manage_board list (#247):
  - Added GET /api/board/features/list endpoint proxying to Studio
  - Added "list" action to manage_board tool with status filter
  - Ava can now query "show me all blocked features" directly

a2a.trace extension (#359):
  - New langfuse-trace extension stamps a2a.trace metadata on all
    outbound A2A dispatches (traceId, callerAgent, skill, project)
  - Quinn reads this to link Langfuse traces across agent boundaries
  - Registered at startup alongside cost/confidence/blast extensions

Closes #247, closes #359.



* fix(pr-remediator): case-insensitive auto-approve prefix matching

"Promote dev to main" titles start with capital P, but the prefix
check was case-sensitive against "promote:". Now lowercases the
title before matching so both "promote:" and "Promote" patterns
are caught.



---------




---------








* feat(ceremony): wire ceremony.security_triage + ceremony.service_health_discord (#431)

Adds CeremonySkillExecutorPlugin — registers FunctionExecutors that bridge
GOAP `ceremony.*` actions to the matching `ceremony.<id>.execute` topic
CeremonyPlugin already listens for. Without this bridge,
SkillDispatcherPlugin dropped every dispatch with "No executor found …"
and (post-#427) emitted HIGH platform.skills_unwired alerts every cycle.

Mirrors the alert-skill-executor-plugin pattern from #427 — explicit
action→ceremony id mapping, install order matters (after registry,
before skill-dispatcher).

Partial fix for #430.




* fix(pr-remediator): wire 5 GOAP-dispatched skills + honor hitlPolicy (#432)

Closes the structural gap where 5 actions in workspace/actions.yaml route
to handlers in PrRemediatorPlugin but had no registered executor:
  - action.pr_update_branch    → pr.remediate.update_branch
  - action.pr_merge_ready      → pr.remediate.merge_ready
  - action.pr_fix_ci           → pr.remediate.fix_ci
  - action.pr_address_feedback → pr.remediate.address_feedback
  - action.dispatch_backmerge  → pr.backmerge.dispatch

Before this change SkillDispatcherPlugin logged "No executor found" and
dropped the dispatch every GOAP cycle. After PR #427's startup validator
the same gap raised platform.skills_unwired HIGH every tick.

Wiring follows the AlertSkillExecutorPlugin pattern from #427:
  - PrRemediatorSkillExecutorPlugin registers FunctionExecutors that
    publish on the existing pr-remediator subscription topics, keeping
    "bus is the contract" — no plugin holds a reference to the other.
  - Executors are fire-and-forget per actions.yaml meta. They return a
    successful SkillResult immediately; pr-remediator's handler runs
    asynchronously on the bus subscription.
  - Install order matches alert-skill-executor: AFTER ExecutorRegistry
    construction, BEFORE skill-dispatcher.

For action.pr_merge_ready specifically, the meta.hitlPolicy
(ttlMs: 1800000, onTimeout: approve) is now honoured. The executor
forwards meta into the trigger payload; _handleMergeReady extracts it
via _extractHitlPolicy and passes it to _emitHitlApproval, which
populates HITLRequest.{ttlMs, onTimeout}. HITLPlugin already auto-
publishes a synthetic approve response when onTimeout=approve fires.

Closes part of #430. Ceremony + protoMaker actions ship in a separate PR.




* feat(agents): register protomaker A2A agent (closes part of #430) (#433)

protoMaker (apps/server in protoLabsAI/ava) has been A2A-ready for a while —
serves agent-card.json with 10 skills including the two referenced by
unwired GOAP actions:
  - action.protomaker_triage_blocked → skill board_health
  - action.protomaker_start_auto_mode → skill auto_mode

Both actions targeted [protomaker], but no agent named "protomaker" was
registered, so the dispatcher couldn't route. Adding the entry closes
the routing gap; A2AExecutor's existing target-matching does the rest.

Endpoint: http://automaker-server:3008/a2a (verified from inside the
workstacean container with AVA_API_KEY → JSON-RPC 2.0 response).




* fix(agents): drop overscoped subscribesTo from protomaker entry (#434)

#433 added `subscribesTo: message.inbound.github.#` to the new protomaker
agent registration, copy-pasted from quinn's pattern. That was wrong:
protoMaker is reached via explicit GOAP `targets: [protomaker]` dispatches
(action.protomaker_triage_blocked, action.protomaker_start_auto_mode), not
as a broadcast inbound listener.

Quinn already subscribes to all GitHub inbound and dispatches `bug_triage`
on protoMaker's behalf. Having protomaker subscribe to the same broadcast
topic is one of the contributing paths to the duplicate-triage spam loop
filed as protoLabsAI/protoMaker#3503 (the root cause is Quinn's handler
not being idempotent — but this cleanup remove…
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